Imports WRDB.Project
Imports WRDB.DBProviders
Imports System.Data.Common
Imports DotSpatial.Symbology
Imports DotSpatial.Topology
Imports DotSpatial.Data
Imports DotSpatial.Controls

Public Class frmGIS

#Region "Declarations"

    'most recently used file list...
    Dim mru As clsMruStripMenu

    Friend Structure structAnimate
        Dim dsData As WRDB.DataSource.clsData 'for animation results
        Dim DataLayer As DotSpatial.Controls.MapPolygonLayer
        Dim DataLayerName, DataAttribute As String, DataField As String
        Dim DataLinkageType As enumLinkageType   'mapping to polygon shapefile
        Dim DataShowVectors As Boolean, DataXParm As String, DataYParm As String, DataArrowLength As Double, DataArrowColor As Color 'more mapping to accomodate vector display
        Dim DataVectorLayer As MapLineLayer
        Dim StationList As Generic.List(Of String) 'list of station IDs found in attribute table (may be station ID, segID, or I,J depending on DataLinkageType)
        Dim CurrentResults As Generic.Dictionary(Of String, Single) 'dictionary of values at each station for current time step
        Dim CurrentVectors As Generic.Dictionary(Of String, Tuple(Of Single, Single)) 'dictionary of lengths and angles at each station for current time step
        Dim DataHandle As Integer
        Dim MinDate, MaxDate, ThisDate, NextDate As Date
        Dim MinValue, MaxValue As Double
        Dim MinVector, MaxVector As Double
        Dim MinNum, MaxNum As Integer
        Dim TimeStep As Double 'time step in days
        Dim TimeUnits As enumTimeUnits
        Dim Speed As Integer 'relative speed from 1 to 5 (1 is slowest)
        Dim Colors() As Color '5 colors for below threshold value, and 0, 1/3, 2/3, and 1 intensities
        Dim InvertColor As Boolean
        Dim MinThresholdValue As Double 'if set to integer.minvalue then is not used
        Dim MaxThresholdValue As Double 'if set to integer.maxvalue then is not used
        Dim NumCategories As Integer
        Dim IsAnimating As Boolean
        Dim ScalingOption As enumScalingOption
        Dim CustomScheme As List(Of Tuple(Of Double, Color))
        Dim OverrideScheme As Boolean 'if true will bypass automatic update of coloring scheme
    End Structure

    Friend Enum enumScalingOption
        DataRange 'scale colors using entire range of data
        StatRange 'scale colors using range of selected statistic
    End Enum

    Private Enum enumStatistic
        Num
        Min
        Max
        Mean
        Median
        Geomean
        Pct5
        Pct10
        Pct25
        Pct75
        Pct90
        Pct95
    End Enum

    Private Enum enumAggregate
        POR
        None
        Day
        Month
        Growing
        Annual
    End Enum

    Private Enum enumField
        FID
        ID
        Name
        Type
        Lat
        Lon
        Freq
        Elev
        Branch
        Reach
        RMile
        Area
        Agency
        RefID
        Remarks
        Other
        HUC
    End Enum

    ''' <summary>
    ''' Controls how layer attributes are linked to data files
    ''' </summary>
    Friend Enum enumLinkageType
        ''' <summary>Require exact match between one attribute field and segment name or metadata</summary>
        StationID

        ''' <summary>Link attribute RECID with segment index</summary>
        SegmentNum

        ''' <summary>Parse segment name to get I,J and link to I and J attribute fields</summary>
        ParseIJK

        ''' <summary>Get I and J from metadata and link to I and J attribute fields</summary>
        IJKtoIJK
    End Enum

    ''' <summary>
    ''' Controls how data are displayed in GIS form
    ''' </summary>
    Public Enum enumMode
        ''' <summary>Display all stations from support table</summary>
        DisplayStations

        ''' <summary>Select from all stations found in browser</summary>
        SelectStations

        ''' <summary>Display summary from last query</summary>
        OutputData

        ''' <summary>Allow data files to be loaded and associated with polygon shapefile; disables display of WRDB Stations data</summary>
        Animate

        ''' <summary>Allow selection of lat/long rectangle</summary>
        AreaOfInterest

        ''' <summary>Display all stations supplied in tab-separated list like Station ID/Name(tab)Latitude(tab)Longitude</summary>
        StationsList

        ''' <summary>Select from polygons displayed on map</summary>
        SelectPolygons

    End Enum

    Private OutputDataOption As clsProject.enumGisOutputOption = clsProject.enumGisOutputOption.Points

    Private GridClippingLayerFilename As String = ""

    Private GridSize As Integer = 100

    Private GridNumPoints As Integer = 5

    Private Structure structMode
        Dim LastSettingsFilename As String
        Dim DataItemsVisible As Boolean
        Dim AllowAnimation As Boolean
        Dim AllowStationSelection As Boolean
        Dim DisplayCoordsByType As Boolean
        Dim SelectStationsFromDataTable As Boolean
        Dim DataTableName As String
        Sub Initialize(ByVal WRDBShapeDirectory As String, ByVal Mode As enumMode)
            LastSettingsFilename = WRDBShapeDirectory & IIf(Mode = enumMode.Animate, "\LastAnim.GIS", "\LastGIS.GIS")
            DataItemsVisible = (Mode = enumMode.Animate Or Mode = enumMode.SelectPolygons)
            AllowAnimation = (Mode = enumMode.Animate Or Mode = enumMode.OutputData)
            AllowStationSelection = (Mode = enumMode.SelectStations Or Mode = enumMode.StationsList Or Mode = enumMode.SelectPolygons)
            DisplayCoordsByType = (Mode = enumMode.DisplayStations Or Mode = enumMode.SelectStations)
            If WRDBProject IsNot Nothing Then
                If Mode = enumMode.SelectStations Then DataTableName = WRDBProject.FullTableName(ActiveTableName) Else DataTableName = TempData
                SelectStationsFromDataTable = (Mode = enumMode.OutputData OrElse (Mode = enumMode.SelectStations AndAlso Not WRDBProject.GisQuickTable))
            End If
        End Sub
    End Structure

    Friend Enum enumTimeUnits
        Minute
        Hour
        Day
        Month
        Year
        None
    End Enum

    ''' <summary>
    ''' Temporary assignments for growing season limits (later allow to be user-defined)
    ''' </summary>
    Const GrowingMonth1 = 3, GrowingDay1 = 15, GrowingMonth2 = 11, GrowingDay2 = 15

    'Private ActiveLayer As clsLayer

    Private Aggregate As enumAggregate = enumAggregate.POR

    Friend AnimationInfo As structAnimate

    ''' <summary>
    ''' Data table containing summary information for each WRDB station
    ''' </summary>
    Private dtResults As DataTable

    ''' <summary>Name of last .GIS file loaded or saved as</summary>
    Private GISFileName As String = ""

    'DotSpatial Controls needed to support plugins...

    '<Export(GetType(SimpleHeaderControl))>
    'Public WithEvents HeaderControl As New SimpleHeaderControl

    '<Export(GetType(SimpleStatusControl))>
    'Public WithEvents StatusControl As New SimpleStatusControl

    '<Export(GetType(SimpleDocking))>
    'Public WithEvents Docking As New SimpleDocking

    '<System.ComponentModel.Composition.Export("Shell", GetType(ContainerControl))>
    'Private Shared Shell As ContainerControl

    ''' <summary>Flag set whenever anything stored in .GIS file changed by user</summary>
    Private IsModified As Boolean = False

    ''' <summary>Cached pixel coordinates of each WRDB stations; to speed up tooltip display when hovering over stations</summary>
    Private lstCoords As New Generic.List(Of Drawing.Point)

    ''' <summary>List of selected stations</summary>
    Private lstStations As New Generic.List(Of String)

    ''' <summary>The form mode, controlling how data are displayed</summary>
    Friend Mode As enumMode

    ''' <summary>Numerous settings that control form based on mode value</summary>
    Private ModeSettings As structMode

    ''' <summary>Layer of WRDBShapefile</summary>
    Private WRDBStationsLayer As DotSpatial.Controls.MapPointLayer

    ''' <summary>Layer of WRDBGridShapefile</summary>
    Friend WRDBGridLayer As DotSpatial.Controls.MapPolygonLayer

    ''' <summary>Grid object used to display spatial interpolation</summary>
    Friend WRDBGrid As clsGrid = Nothing

    ''' <summary>Temporary directory where WRDB Stations shapefile is saved (as well as all other shapefiles downloaded)</summary>
    Friend LayerDirectory As String

    ''' <summary>Directory where cache of downloaded shapefiles will be saved (global to all projects, so stored in registry)</summary>
    Friend CacheDirectory As String

    ''' <summary>GIS map background color</summary>
    Friend BackgroundColor As String

    ''' <summary>If true, animation parameters will not be updated (used when multiple comboboxes need to be updated during LoadSettings)</summary>
    Private SuspendMapUpdate As Boolean

    ''' <summary>Handle of drawing layer for AOI rectangle</summary>
    Private AOIDrawingHandle As Integer = -1

    ''' <summary>Coordinates of AOI rectangle</summary>
    Private _AOIRectangle As RectangleF

    ''' <summary>Set this true if want last graph to be loaded once form is loaded</summary>
    Public LoadLast As Boolean = False

    ''' <summary>Set this to full path of file if want it to be loaded once form is loaded</summary>
    Public LoadFile As String = ""

    ''' <summary>Set to true when form is closed so can possibly interrupt long-running load process</summary>
    Private IsCancelled As Boolean = False

    Friend WithEvents WebMap As WRDBGIS.WebMap.clsWebMap

    ''' <summary>
    ''' This is used to update the UI from another thread
    ''' </summary>
    ''' <remarks>
    ''' SynchContext is initialized in the Form.Load event
    ''' Use it to post messages from a background to the UI thread (see WriteProgress)
    ''' </remarks>
    Private SynchContext As WindowsFormsSynchronizationContext

    Private Enum enumMapMode
        [Select]
        ZoomWin
        ZoomPan
        XSect
        PolygonCreate
        PolylineCreate
        PointCreate
        PolygonSelect
        PolylineSelect
    End Enum

    Private _MapMode As enumMapMode

    ''' <summary>Current mode set when selecting, adding, moving, deleting points</summary>
    Private Property MapMode As enumMapMode
        Get
            Return _MapMode
        End Get
        Set(value As enumMapMode)
            _MapMode = value
            mnuViewSelect.Checked = False
            mnuSelect2.Checked = False
            'tbSelect.Checked = False
            mnuZoomWindow.Checked = False
            mnuZoomWindow2.Checked = False
            tbZoomWindow.Checked = False
            mnuPan.Checked = False
            mnuPan2.Checked = False
            tbPan.Checked = False
            With GISMap
                Select Case value
                    Case enumMapMode.Select
                        .ActivateMapFunction(New MapFunctionSelectPan(GISMap))
                        mnuViewSelect.Checked = True
                        mnuSelect2.Checked = True
                        'tbSelect.Checked = True
                        StatusLabel.Text = "Ready"
                    Case enumMapMode.ZoomWin
                        .FunctionMode = FunctionMode.ZoomIn
                        mnuZoomWindow.Checked = True
                        mnuZoomWindow2.Checked = True
                        tbZoomWindow.Checked = True
                        StatusLabel.Text = "Click on corners of window to zoom in to..."
                    Case enumMapMode.ZoomPan
                        .FunctionMode = FunctionMode.Pan
                        mnuPan.Checked = True
                        mnuPan2.Checked = True
                        tbPan.Checked = True
                        StatusLabel.Text = "Click and drag to pan..."
                    Case enumMapMode.PolygonCreate, enumMapMode.PolygonSelect, enumMapMode.PolylineCreate, enumMapMode.PolylineSelect
                        .FunctionMode = FunctionMode.None
                        StatusLabel.Text = "Click first point to form the polygon or polyline, then click for each additional vertex (ESC to cancel)..."
                        lstPolygon.Clear()
                    Case enumMapMode.XSect
                        .FunctionMode = FunctionMode.None
                        StatusLabel.Text = "Move mouse to beginning of cross section then click and drag (release and end of cross section)..."
                End Select
            End With
        End Set
    End Property

    ''' <summary>Coordinates of active polygon, polyline, or xsec when selecting</summary>
    Private lstPolygon As New List(Of Coordinate)

    ''' <summary>List of selected 3D cells (I,J,K); used when select by file</summary>
    Private lst3DCells As New List(Of Tuple(Of Integer, Integer, Integer))

    ''' <summary>Will be set to true when selecting 3D zones by file; required so all other means of selecting will clear lst3DCells</summary>
    Private IsSelectingByFile As Boolean = False

#End Region

#Region "Drag-drop support for layer list"


    Private IsDragging As Boolean = False
    Private itemIndexDrag, itemIndexDrop As Integer

    'following needs work--probably not worth it!

    'Private Sub lstLayers_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstLayers.DragEnter
    '    If e.Data.GetDataPresent(DataFormats.StringFormat) And e.AllowedEffect = DragDropEffects.Move Then e.Effect = DragDropEffects.Move
    'End Sub

    'Private Sub lstLayers_DragOver(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstLayers.DragOver
    '    itemIndexDrop = lstLayers.IndexFromPoint(lstLayers.PointToClient(New Point(e.X, e.Y)))
    '    IsDragging = True
    '    lstLayers.SelectedIndex = itemIndexDrop
    'End Sub

    'Private Sub lstLayers_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles lstLayers.DragDrop
    '    If e.Data.GetDataPresent(DataFormats.StringFormat) Then
    '        If itemIndexDrop >= 0 And itemIndexDrop < lstLayers.Items.Count Then
    '            lstLayers.Items.Insert(itemIndexDrop, e.Data.GetData(DataFormats.Text))
    '        Else 'add to bottom of list
    '            lstLayers.Items.Add(e.Data.GetData(DataFormats.Text))
    '        End If
    '        IsDragging = False
    '    End If
    'End Sub

    'Private Sub lstLayers_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles lstLayers.MouseDown
    '    itemIndexDrag = lstLayers.IndexFromPoint(e.X, e.Y)
    '    If itemIndexDrag >= 0 And itemIndexDrag < lstLayers.Items.Count Then
    '        lstLayers.DoDragDrop(lstLayers.Items(itemIndexDrag), DragDropEffects.Move)
    '    End If
    'End Sub

    'Private Sub lstLayers_ItemCheck(ByVal sender As Object, ByVal e As System.Windows.Forms.ItemCheckEventArgs) Handles lstLayers.ItemCheck
    '    If ActiveLayer IsNot Nothing Then ActiveLayer.Visible = (e.NewValue = CheckState.Checked)
    'End Sub

    'Private Sub tvLayers_AfterCheck(sender As Object, e As System.Windows.Forms.TreeViewEventArgs)
    '    If e.Action = TreeViewAction.ByKeyboard Or e.Action = TreeViewAction.ByMouse Then
    '        If e.Node.Nodes.Count > 0 Then 'is group; set check for all children
    '            For Each n As TreeNode In e.Node.Nodes
    '                n.Checked = e.Node.Checked
    '            Next
    '        ElseIf e.Node.Parent IsNot Nothing Then
    '            Dim someChecked As Boolean = False
    '            For Each n As TreeNode In e.Node.Parent.Nodes
    '                If n.Checked Then someChecked = True : Exit For
    '            Next
    '            e.Node.Parent.Checked = someChecked
    '        End If
    '        If ActiveLayer IsNot Nothing Then ActiveLayer.Visible = e.Node.Checked
    '    End If
    'End Sub

    'Private Sub tvLayers_AfterSelect(sender As Object, e As System.Windows.Forms.TreeViewEventArgs)
    '    Try
    '        With tvLayers
    '            If Not IsDragging Then
    '                If e.Node Is Nothing Then
    '                    ActiveLayer = Nothing
    '                Else
    '                    If e.Node.Nodes.Count > 0 Then 'must be a group
    '                        ActiveLayer = clsLayer.Reset(GISMap, e.Node.Text)
    '                    ElseIf e.Node.Parent IsNot Nothing Then
    '                        Dim grp As DotSpatial.Symbology.Group = GISMap.Layers(e.Node.Parent.Index)
    '                        ActiveLayer = clsLayer.Reset(GISMap, CType(grp.GetLayers(e.Node.Index), DotSpatial.Symbology.Layer))
    '                    Else
    '                        ActiveLayer = clsLayer.Reset(GISMap, .Nodes.Count - 1 - .SelectedNode.Index())
    '                    End If
    '                End If
    '            End If
    '        End With
    '        If ActiveLayer IsNot Nothing Then
    '            pgLayers.SelectedObject = ActiveLayer
    '            CurrentLayer = ActiveLayer.Layer  'for typeconverter
    '        End If

    '        'clear info on prior object hovered over
    '        txtInfo.Text = ""

    '        'clear selected shapes and only allow shapes in active layer to be selected
    '        With GISMap
    '            .Layers.SuspendEvents()
    '            Dim ext As New DotSpatial.Topology.Envelope(.ViewExtents.X, .ViewExtents.Y, .ViewExtents.X + .ViewExtents.Width, .ViewExtents.Y + .ViewExtents.Height)
    '            .ClearSelection(ext)
    '            For Each lyr As DotSpatial.Controls.IMapLayer In .Layers
    '                lyr.SelectionEnabled = (ActiveLayer IsNot Nothing AndAlso lyr.LegendText = ActiveLayer.Name)
    '            Next
    '            .Layers.ResumeEvents()
    '        End With
    '    Catch ex As Exception
    '        ErrorMsg(, ex)
    '    End Try
    'End Sub

    'Private Sub lstLayers_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstLayers.SelectedIndexChanged
    '    Try
    '        With lstLayers
    '            If Not IsDragging Then
    '                If .SelectedIndex = -1 Then
    '                    ActiveLayer = Nothing
    '                Else
    '                    ActiveLayer = clsLayer.Reset(GISMap, .Items.Count - 1 - .SelectedIndex)
    '                End If
    '            End If
    '        End With
    '        If ActiveLayer IsNot Nothing Then
    '            pgLayers.SelectedObject = ActiveLayer
    '            CurrentLayer = ActiveLayer.Layer  'for typeconverter
    '        End If

    '        'clear info on prior object hovered over
    '        txtInfo.Text = ""

    '        'clear selected shapes and only allow shapes in active layer to be selected
    '        With GISMap
    '            .Layers.SuspendEvents()
    '            Dim ext As New DotSpatial.Topology.Envelope(.ViewExtents.X, .ViewExtents.Y, .ViewExtents.X + .ViewExtents.Width, .ViewExtents.Y + .ViewExtents.Height)
    '            .ClearSelection(ext)
    '            For Each lyr As DotSpatial.Controls.IMapLayer In .Layers
    '                lyr.SelectionEnabled = (ActiveLayer IsNot Nothing AndAlso lyr.LegendText = ActiveLayer.Name)
    '            Next
    '            .Layers.ResumeEvents()
    '        End With
    '    Catch ex As Exception
    '        ErrorMsg(, ex)
    '    End Try
    'End Sub
#End Region

#Region "Form events"


    ''' <summary>
    ''' Instantiate new GIS form and initialize (will always load last GIS file associated with mode)
    ''' </summary>
    ''' <param name="_WRDBProject">WRDB Project (to get default settings, etc.)</param>
    ''' <param name="_TableName">Currently active data table (Select mode)</param>
    ''' <param name="_TempData">Name of temp table (Display mode)</param>
    ''' <param name="_Mode">GIS display mode</param>
    ''' <param name="_WRDBStationTypes">List of available station types to speed processing</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal _WRDBProject As clsProject, ByVal _TableName As String, ByVal _TempData As String, ByVal _Mode As frmGIS.enumMode, ByVal _WRDBStationTypes As Generic.List(Of String), _BaseMap As String)

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        WRDBProject = _WRDBProject
        ActiveTableName = _TableName
        TempData = _TempData
        WRDBStationTypes = _WRDBStationTypes
        GISForm = Me
        LoadFile = _BaseMap
        Mode = _Mode
        OutputDataOption = WRDBProject.GisOutputOption
        GridClippingLayerFilename = WRDBProject.GisClippingLayerFilename
        GridSize = WRDBProject.GisOutputGridSize
        GridNumPoints = WRDBProject.GisNumPoints
    End Sub

    ''' <summary>
    ''' Instantiate new GIS form and initialize
    ''' </summary>
    ''' <param name="GisFile">GIS file to load</param>
    ''' <param name="LoadLast">Flag indicating if last map should be loaded</param>
    ''' <remarks></remarks>
    Public Sub New(Optional ByVal GisFile As String = "", Optional ByVal LoadLast As Boolean = False)

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.LoadFile = GisFile
        Me.LoadLast = LoadLast
        WRDBProject = Nothing
        ActiveTableName = ""
        TempData = ""
        GISForm = Me
        WRDBStationTypes.Clear()
        Mode = enumMode.Animate
        'ModeSettings.Initialize(Mode)
    End Sub

    ''' <summary>
    ''' Instantiate new GIS form and initialize
    ''' </summary>
    ''' <param name="_Mode">GIS display mode</param>
    ''' <param name="_StationList">Tab-separated stations list like Station ID/Name(tab)Latitude(tab)Longitude</param>
    Public Sub New(ByVal _Mode As frmGIS.enumMode, Optional ByVal _StationList As Generic.List(Of String) = Nothing, Optional _Basemap As String = "")

        ' This call is required by the Windows Form Designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.LoadFile = _Basemap
        WRDBProject = Nothing
        ActiveTableName = ""
        TempData = ""
        GISForm = Me
        StationList = _StationList
        WRDBStationTypes.Clear()
        Mode = _Mode
    End Sub

    Private Sub CheckDropDownItem(ByVal ParentMenuItem As Object, ByVal Index As Integer)
        For i As Integer = 0 To ParentMenuItem.DropDownItems.Count - 1
            CType(ParentMenuItem.DropDownItems(i), ToolStripMenuItem).Checked = i = Index
        Next
    End Sub

    Private Sub CheckDropDownItem(ByVal ParentMenuItem As ToolStripMenuItem, ByVal DropDownItem As ToolStripMenuItem)
        For i As Integer = 0 To ParentMenuItem.DropDownItems.Count - 1
            CType(ParentMenuItem.DropDownItems(i), ToolStripMenuItem).Checked = ParentMenuItem.DropDownItems(i) Is DropDownItem
        Next
    End Sub

    Private Sub frmGIS_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        'note: not called if parent WRDB application exits
        Try
            SaveSetting(REGAPP, "WebMap", "Opacity", WebMap.Opacity)

            ' Call sub to save the MRU filenames
            mru.SaveToRegistry()

            'in case is in middle of long operation
            If AnimationInfo.IsAnimating Then
                AnimationInfo.IsAnimating = False
                AnimTimer.Stop()
            End If

            IsCancelled = True
            Application.DoEvents()

            If Not My.Computer.FileSystem.DirectoryExists(TempPath) Then My.Computer.FileSystem.CreateDirectory(TempPath)
            If dtResults IsNot Nothing Then dtResults.Dispose()
            GISForm = Nothing
            If WRDBStationTypes IsNot Nothing Then WRDBStationTypes.Clear()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub frmGIS_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        Try
            If IsModified And GISMap.Layers.Count > 0 AndAlso Not (GISMap.Layers.Count = 1 AndAlso WRDBStationsLayer IsNot Nothing) Then
                Select Case SaveChangesWarning(GISFileName)
                    Case Windows.Forms.DialogResult.Yes : e.Cancel = Not SaveSettings(GISFileName)
                    Case Windows.Forms.DialogResult.No
                    Case Windows.Forms.DialogResult.Cancel : e.Cancel = True
                End Select
            End If
            If e.Cancel Then IsCancelled = True : Application.DoEvents() : Exit Sub
            SaveWindowPos(REGAPP, Me)
            SaveSetting(REGAPP, Me.Name, "SplitMainCollapsed", splitMain.IsCollapsed)
            SaveSetting(REGAPP, Me.Name, "SplitMainDistance", splitMain.ExpandedDistance)
            SaveSetting(REGAPP, Me.Name, "SplitInfoDistance", splitInfo.SplitterDistance)
            SaveSetting(REGAPP, Me.Name, "Aggregate", Aggregate)
            SaveSetting(REGAPP, Me.Name, "LayerDir", LayerDirectory)
            SaveSetting(REGAPP, Me.Name, "CacheDir", CacheDirectory)
            SaveSetting(REGAPP, Me.Name, "BackgroundColor", BackgroundColor)

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ExceptionHandler(sender As Object, t As Threading.ThreadExceptionEventArgs)
        ProgressComplete()
        ErrorMsg(, t.Exception)
    End Sub

    Private Sub frmGIS_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Application.EnableVisualStyles()
        Me.SynchContext = Threading.SynchronizationContext.Current

        Me.Enabled = False 'will reenable once form is shown

        'attach form exception handler
        AddHandler Application.ThreadException, AddressOf ExceptionHandler

        ResetTitleBar()

        GetWindowPos(REGAPP, Me)
        splitMain.IsCollapsed = GetSetting(REGAPP, Me.Name, "SplitMainCollapsed", False)
        splitMain.ExpandedDistance = GetSetting(REGAPP, Me.Name, "SplitMainDistance", 200)

        Try
            Me.splitMain.Panel2.Controls.Add(GISMap)
        Catch ex As Exception
            ErrorMsg("Unable to display GIS form because the MapWindow control has not been installed. Please download and install it from the WRDB web site.", ex)
            Close()
            Exit Sub
        End Try

        With GISMap
            .Dock = DockStyle.Fill
            .ProjectionModeDefine = DotSpatial.Controls.ActionMode.Never
            .ProjectionModeReproject = DotSpatial.Controls.ActionMode.Never
            'remove context menu so right-click will cancel operation
            '.ContextMenuStrip = ContextMenuMap
            .AllowDrop = True 'but will let form handle file drops
            '.ProgressHandler = New clsMapProgressHandler
        End With

        'retrieve default toolbar settings, but override depending on the mode

        frmToolbar.GetToolbarSettings(tbMain)

    End Sub

    Private Sub frmGIS_Shown(sender As Object, e As EventArgs) Handles Me.Shown
        Try
            WriteProgress("Preparing GIS form...")

            Dim sw As New Stopwatch

            'initialize mru (most recently used) menu list

            mru = New clsMruStripMenu(mnuFileRecent, AddressOf RecentFileHandler, "Software\VB and VBA Program Settings\WRDBGRAPH\GIS Files", 9)
            With mru
                .MaxShortenPathLength = 30
                .LoadFromRegistry()

                'make sure all these mru files exist
                For i As Integer = .NumEntries - 1 To 0 Step -1
                    If Not My.Computer.FileSystem.FileExists(.GetFileAt(i)) Then .RemoveFile(i)
                Next
                .SaveToRegistry()
            End With

            'note: for the DotSpatial AppManager to work correctly, by trial and error it was determined that it must be initialized in the caller as follows
            '
            '<System.ComponentModel.Composition.Export("Shell", GetType(ContainerControl))>
            'Private Shell As ContainerControl
            '
            'and then with this form is instantiated, do:
            '
            'Shell = frm

            Dim GISAppManager As New DotSpatial.Controls.AppManager
            With GISAppManager
                .Directories.Clear()
                .Directories.Add("Plugins")
                .Directories.Add("Application Extensions")
                '.ShowExtensionsDialog = DotSpatial.Controls.ShowExtensionsDialog.None
                .ShowExtensionsDialogMode = ShowExtensionsDialogMode.None
                .Map = GISForm.GISMap
                .Legend = GISForm.GISLegend
                .SerializationManager = New DotSpatial.Controls.SerializationManager(GISAppManager)
                .DockManager = New GISDockManager
                .ProgressHandler = New GISStatusControl
                .HeaderControl = New GISHeaderControl
                .LoadExtensions()
            End With

            GISLegend.AddMapFrame(GISMap.MapFrame)

            Aggregate = GetSetting(REGAPP, Me.Name, "Aggregate", enumAggregate.POR)

            'populate list of basemaps available (first two items already set to Set Opacity and a separator)

            WebMap = New WRDBGIS.WebMap.clsWebMap(GISMap)
            WebMap.Opacity = GetSetting(REGAPP, "WebMap", "Opacity", 100)

            For Each s As String In WebMap.GetDefaultServiceProviders
                If s <> "Google Satellite" Then
                    mnuLayerBasemap.DropDownItems.Add(s)
                    cboLayerBasemap.Items.Add(s)
                End If
                'If s = "Google Satellite" Then mnuLayerBasemap.DropDownItems(mnuLayerBasemap.DropDownItems.Count - 1).Visible = False '5/20/15 no longer supported
            Next

            CType(mnuLayerBasemap.DropDownItems(2), ToolStripMenuItem).Checked = True 'start with NONE checked
            cboLayerBasemap.SelectedIndex = 0

            GISForm = Me
            ProjectProjection = New DotSpatial.Projections.ProjectionInfo()
            ProjectProjection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984

            Dim WRDBDirectory As String = My.Computer.FileSystem.SpecialDirectories.MyDocuments & "\WRDB"

            LayerDirectory = GetSetting(REGAPP, Me.Name, "LayerDir", WRDBDirectory & "\GIS Layers")
            CacheDirectory = GetSetting(REGAPP, Me.Name, "CacheDir", WRDBDirectory & "\GIS Cache")

            BackgroundColor = GetSetting(REGAPP, Me.Name, "BackgroundColor", Color.White.Name)
            GISMap.BackColor = Color.FromName(BackgroundColor)

            'if project's GIS directory has been set and exists, use it instead of temporary directory

            If WRDBProject IsNot Nothing AndAlso WRDBProject.GisDirectory <> "" Then LayerDirectory = WRDBProject.GisDirectory

            Try
                If Not My.Computer.FileSystem.DirectoryExists(WRDBDirectory) Then My.Computer.FileSystem.CreateDirectory(WRDBDirectory)
            Catch ex As Exception
                WarningMsg("Unable to find or create this directory: {0}.\n\nPlease reset the directory under Project Preferences.", WRDBDirectory)
                'Exit Sub
            End Try

            'reference MapWinUtility here to force builder to include log in desired folder
            MapWinUtility.Log.SetLogFile(IO.Path.Combine(WRDBDirectory, "WRDBGis.log"))

            Try
                If Not My.Computer.FileSystem.DirectoryExists(LayerDirectory) Then My.Computer.FileSystem.CreateDirectory(LayerDirectory)
            Catch ex As Exception
                WarningMsg("Unable to find or create this directory: {0}.\n\nPlease reset the directory under Project Preferences.", LayerDirectory)
                LayerDirectory = WRDBDirectory & "\GIS Layers"
                'Exit Sub
            End Try

            Try
                If Not My.Computer.FileSystem.DirectoryExists(CacheDirectory) Then My.Computer.FileSystem.CreateDirectory(CacheDirectory)
            Catch ex As Exception
                WarningMsg("Unable to find or create this directory: {0}.\n\nPlease reset the directory under Project Preferences.", CacheDirectory)
                LayerDirectory = WRDBDirectory & "\GIS Cache"
                'Exit Sub
            End Try

            WRDBStationsFileName = String.Format("{0}\{1}.shp", LayerDirectory, WRDBFileName)
            WRDBGridFileName = String.Format("{0}\{1}.shp", LayerDirectory, IO.Path.GetFileNameWithoutExtension(WRDBGridFileName))

            For i As Integer = 0 To DefaultLayerColor.Length - 1
                DefaultLayerColor(i) = Choose((i Mod 14) + 1, Color.Red, Color.Blue, Color.Green, Color.DarkTurquoise, Color.Magenta, Color.Orange, Color.Gray, Color.Black,
                                                              Color.DarkRed, Color.DarkBlue, Color.DarkGreen, Color.DarkCyan, Color.DarkMagenta, Color.SaddleBrown)
            Next

            With AnimationInfo
                ReDim .Colors(4)
                .Colors(0) = Color.LightGray
                .Colors(1) = Color.Green
                .Colors(2) = Color.Yellow
                .Colors(3) = Color.Red
                .Colors(4) = Color.White
                .MinThresholdValue = Integer.MinValue
                .MaxThresholdValue = Integer.MaxValue
                .InvertColor = False
                .Speed = 5
                .NumCategories = 50 'determines how fine the color changes will be
                .CustomScheme = New List(Of Tuple(Of Double, Color))
                .OverrideScheme = False
            End With

            IsModified = False

            ModeSettings.Initialize(LayerDirectory, Mode)

            'control visibility of data items
            Dim tf As Boolean = ModeSettings.AllowAnimation

            mnuData.Visible = tf
            mnuFileExport.Visible = tf
            mnuFileMovie.Visible = tf
            mnuFileSepMovie.Visible = tf
            mnuViewSepAnimate.Visible = tf
            mnuViewAnimate.Visible = tf
            mnuViewAnimateSpeed.Visible = tf
            mnuDataDateTime.Visible = tf
            mnuDataAggregation.Visible = tf
            mnuDataVariable.Visible = tf
            mnuDataStatistic.Visible = tf
            mnuDataOptions.Visible = tf
            mnuDataSepOptions.Visible = tf
            tblData.Visible = (Mode = enumMode.OutputData)
            tblAnim.Enabled = tf And (Mode = enumMode.OutputData)
            mnuGraph.Visible = tf
            tbGraphTS.Visible = tf

            tf = Mode = enumMode.Animate

            'if animating, make these visible immediately (after dataset open will use DropDownOpening to control visibility)
            mnuDataOpen.Visible = tf
            mnuDataClose.Visible = tf
            mnuDataConfig.Visible = tf
            mnuDataSepOpen.Visible = tf
            mnuDataLayer.Visible = tf
            mnuViewData.Visible = tf
            mnuViewLegend.Visible = tf

            '...otherwise
            lblLocation.Visible = Not tf
            txtLocation.Visible = Not tf
            btnLocation.Visible = Not tf
            sepLocation.Visible = Not tf

            tbMain.Refresh()
            Application.DoEvents()

            GISMap.Visible = False
            SuspendMapUpdate = True
            cboStatistic.Items.Clear()
            cboStationType.Visible = False

            WriteProgress("Compiling data...")

            'control contents of toolbar combos and submenus
            Select Case Mode
                Case enumMode.OutputData, enumMode.Animate
                    Dim ar() As String = {"Count", "Minimum", "Maximum", "Mean"} ', "Median", "Geomean", "5th Percentile", "10th Percentile", "25th Percentile", "75th Percentile", "90th Percentile", "95th Percentile"}
                    cboStatistic.Items.AddRange(ar)
                    cboStatistic.SelectedIndex = 3
                    With mnuDataStatistic.DropDownItems
                        For Each s As String In ar
                            .Add(s)
                        Next
                    End With
                    CheckDropDownItem(mnuDataStatistic, cboStatistic.SelectedIndex)

                    If Mode = enumMode.OutputData Then
                        'only retrieve pcodes that can be displayed on the map
                        Dim Sql As String = ""
                        If WRDBProject.GisQuickList Then
                            Sql = $"SELECT PCode FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.PCodes)} PCodes ORDER BY PCode"
                        Else
                            Sql =
$"SELECT DISTINCT 
    Dta.PCode 
FROM ({TempData} Dta 
LEFT OUTER JOIN {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} STATIONS ON Dta.Station_ID=Stations.Station_ID) 
WHERE
    Latitude IS NOT NULL 
    AND Longitude IS NOT NULL 
    AND Latitude <> 0.0 
    AND Longitude <> 0.0 
    AND Selected_By='{clsApplicationSettings.UserName}' 
ORDER BY Dta.PCode"
                        End If
                        'For Each s As String In WRDBProject.DB.GetRecordList(IIf(WRDBProject.GisQuickList, "PCodes", TempData), "PCODE", IIf(WRDBProject.GisQuickList, "", String.Format("WHERE Selected_By='{0}'", clsApplicationSettings.UserName)))
                        Dim dr As DbDataReader = WRDBProject.DB.ExecuteReader(Sql)
                        cboVariable.SuspendLayout()
                        While dr IsNot Nothing AndAlso dr.Read
                            If Not dr.IsDBNull(0) Then
                                Dim s As String = dr.GetString(0)
                                cboVariable.Items.Add(s)
                                mnuDataVariable.DropDownItems.Add(s)
                            End If
                        End While
                        dr.Close()
                        cboVariable.ResumeLayout()
                        lblLayer.Enabled = False
                        cboLayer.Enabled = False
                    End If
                    If cboVariable.Items.Count > 0 Then
                        cboVariable.SelectedIndex = 0
                        CheckDropDownItem(mnuDataVariable, 0)
                    End If

                    With cboAggregation.Items
                        .Clear()
                        If True Or Mode = enumMode.Animate Then .Add("None")
                        .Add("Period of Record")
                        .Add("Day")
                        .Add("Month")
                        .Add("Growing Season")
                        .Add("Annual")
                    End With
                    cboAggregation.SelectedIndex = IIf(Mode = enumMode.Animate, 0, 1)
                    ResetAggregate()

                    If Mode = enumMode.Animate Then
                        mnuAggregateNone.Visible = True
                        CheckDropDownItem(mnuDataAggregation, 0)
                    Else
                        mnuAggregateNone.Visible = False
                        CheckDropDownItem(mnuDataAggregation, 1)
                    End If

                    tblAnim.Enabled = Aggregate <> enumAggregate.POR

                    With AnimationInfo
                        CheckDropDownItem(mnuViewAnimateSpeed, .Speed - 1)
                        CheckDropDownItem(tbAnimSpeed, .Speed - 1)
                    End With

                Case enumMode.DisplayStations
                    cboStationType.Visible = True
                    cboStationType.Items.Add("All Stations")
                    For Each s As String In WRDBProject.DB.GetRecordList("Stations", "Station_Type")
                        cboStationType.Items.Add(s)
                    Next
                    Dim NumNull As Integer = WRDBProject.DB.ExecuteScalar($"SELECT COUNT(*) FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} Stations WHERE Station_Type IS NULL")
                    If NumNull > 0 Then cboStationType.Items.Add("<Null>")
                    cboStationType.SelectedIndex = 0

                Case enumMode.SelectStations, enumMode.StationsList
                    cboStationType.Visible = True
                    cboStationType.Items.Add("All Stations")
                    If WRDBStationTypes.Count = 0 Then
                        Dim SQL As String
                        If Mode = enumMode.StationsList Then
                            For Each s As String In StationList
                                Dim StationType As String = s.Split(vbTab)(2)
                                If StationType <> "" And Not cboStatistic.Items.Contains(StationType) Then
                                    cboStationType.Items.Add(StationType)
                                End If
                            Next
                        Else
                            If WRDBProject.GisQuickList Then
                                SQL = $"SELECT DISTINCT Station_Type FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} STATIONS"
                            Else
                                SQL = $"SELECT DISTINCT Station_Type FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} STATIONS, {WRDBProject.FullTableName(ActiveTableName)} Dta WHERE STATIONS.STATION_ID=Dta.STATION_ID"
                            End If
                            Dim dr As DbDataReader = WRDBProject.DB.ExecuteReader(SQL)
                            While dr IsNot Nothing AndAlso dr.Read
                                If Not dr.IsDBNull(0) Then
                                    cboStationType.Items.Add(dr.GetString(0))
                                End If
                            End While
                            dr.Close()
                        End If
                        With WRDBStationTypes
                            .Clear()
                            For Each s As String In cboStationType.Items
                                .Add(s)
                            Next
                        End With
                    Else
                        For Each s As String In WRDBStationTypes
                            cboStationType.Items.Add(s)
                        Next
                    End If
                    Dim NumNull As Integer = WRDBProject.DB.ExecuteScalar($"SELECT COUNT(*) FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} Stations WHERE Station_Type IS NULL")
                    If NumNull > 0 Then cboStationType.Items.Add("<Null>")
                    cboStationType.SelectedIndex = 0

                Case enumMode.SelectPolygons
                    'mnuDataDisplay.Visible = False

                Case enumMode.AreaOfInterest
                    mnuDataStatistic.Visible = False

            End Select

            cboStatistic.MaxDropDownItems = Math.Min(25, Math.Max(1, cboStatistic.Items.Count))
            cboVariable.MaxDropDownItems = 15

            If LoadLast Then
                Dim LastGIS As String = ""
                If LastGIS <> "" Then
                    LoadSettings(LastGIS, False)
                    ResetAnimation()
                    RefreshStationsDisplay()
                End If
                'mnuFileOpenLast.PerformClick()
            End If

            WriteProgress("Summarizing data...")

            If Not LoadLast Then
                Select Case Mode
                    Case enumMode.Animate
                        Dim fn As String = GetSetting(REGAPP, "AutoLoad", "Animation")
                        If Not String.IsNullOrWhiteSpace(fn) Then LoadFile = fn
                End Select

                If LoadFile <> "" Then
                    If My.Computer.FileSystem.FileExists(LoadFile) Then
                        LoadSettings(LoadFile, True)
                    Else
                        WarningMsg("The specified basemap file ({0}) could not be found.", LoadFile)
                    End If
                Else
                    cboAggregation.SelectedIndex = IIf(Mode = enumMode.Animate, 0, 1)
                    GISMap.ZoomToMaxExtent()
                End If
                ResetAnimation()
            End If

            'create this later so basemap projection will already be known
            Select Case Mode
                Case enumMode.DisplayStations, enumMode.SelectStations
                    CreateStationsLayer()
                Case enumMode.OutputData
                    SummarizeStationData()
                    CreateStationsLayer()
                    If OutputDataOption <> clsProject.enumGisOutputOption.Points Then
                        If WRDBProject.GisClippingLayerFilename <> "" Then 'see if already loaded
                            If GetLayer(WRDBProject.GisClippingLayerFilename) Is Nothing AndAlso AddLayer(WRDBProject.GisClippingLayerFilename) Then
                                moveLayerBottom(GetLayer(WRDBProject.GisClippingLayerFilename))
                            End If
                        End If
                        CreateGridLayer()
                        CreateGridScheme()
                        RefreshGrid()
                    End If
                    If OutputDataOption = clsProject.enumGisOutputOption.Grid Then
                        WRDBStationsLayer.IsVisible = False
                        WRDBStationsLayer.LegendItemVisible = False
                    End If
                    Collapse_Expand_All(True) 'collapse all legend entries
                Case enumMode.Animate
                    tbDataTable.Visible = True
                    tbLegend.Visible = True
                Case enumMode.StationsList
                    CreateStationsLayer(StationList)
                Case enumMode.AreaOfInterest
            End Select

            tbSelect.PerformClick()

            SuspendMapUpdate = False

            If LoadFile = "" Then SetExtentsAndReproject()

            IsModified = False

            If sw.Elapsed.TotalSeconds > 30 Then
                frmInfoMsg.ShowInfoMsg("GIS Performance", "It took a considerable amount of time to display the GIS form. Consider changing your GIS 'Quick' settings under Project preferences and/or add indices to data tables.")
            End If

            Enabled = True

        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            ProgressComplete()
            GISMap.Visible = True
            RefreshStationsDisplay()
        End Try
    End Sub

    Private Sub RecentFileHandler(ByVal Number As Integer, ByVal FileName As String)
        LoadSettings(FileName, False)
        ResetAnimation()
        RefreshStationsDisplay()

        ' Call sub to reorder the MRU list
        mru.AddFile(FileName)
        mru.SaveToRegistry()
        GISFileName = FileName
        ResetTitleBar()
    End Sub

    Private Sub frmGIS_DragDrop(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles MyBase.DragDrop
        For i As Integer = 0 To e.Data.GetData(DataFormats.FileDrop).length - 1
            Dim FileName As String = e.Data.GetData(DataFormats.FileDrop)(i)
            Select Case IO.Path.GetExtension(FileName).ToUpper
                Case ".GIS"
                    GISFileName = FileName
                    ResetTitleBar()
                    LoadSettings(FileName, False)
                    ResetAnimation()
                    RefreshStationsDisplay()
                    mru.AddFile(GISFileName) 'Call sub to add this file as an MRU

                Case ".SHP"
                    AddLayer(FileName)

            End Select
        Next
    End Sub

    Private Sub frmGIS_DragEnter(ByVal sender As Object, ByVal e As System.Windows.Forms.DragEventArgs) Handles MyBase.DragEnter
        If e.Data.GetDataPresent(DataFormats.FileDrop) Then
            Dim FileName As String = e.Data.GetData(DataFormats.FileDrop)(0)
            Select Case IO.Path.GetExtension(FileName).ToUpper
                Case ".GIS", ".SHP"
                    e.Effect = DragDropEffects.Copy
                Case Else
                    e.Effect = DragDropEffects.None
            End Select
        End If
    End Sub

    Private Sub frmGIS_Resize(sender As Object, e As EventArgs) Handles Me.Resize
        'if already arranged windows with graphs, resize all at the same time
        With Me
            If .WindowState = FormWindowState.Normal AndAlso .Location = New System.Drawing.Point(0, 0) AndAlso .Height = My.Computer.Screen.WorkingArea.Height Then
                For Each f As WRDB.Graph.frmGraph In GraphForms()
                    If f.FormBorderStyle <> Windows.Forms.FormBorderStyle.Sizable Then
                        f.Location = New System.Drawing.Point(Me.Size.Width, f.Location.Y)
                        f.Width = My.Computer.Screen.WorkingArea.Width - Me.Width
                    End If
                Next
            End If
        End With
    End Sub

    Private Sub GISLegend_Click(sender As Object, e As EventArgs) Handles GISLegend.Click
        'if change selected layer, only allow object selection on that layer
        For Each lyr In GISMap.Layers
            If lyr Is GISMap.Layers.SelectedLayer Then
                lyr.SelectionEnabled = True
            Else
                ClearSelection(lyr)
                lyr.SelectionEnabled = False
            End If
        Next
    End Sub

    Private Sub ClearSelection(lyr As Layer)
        'this is necessary because lyr.ClearSelection does not work
        If TypeOf lyr Is FeatureLayer Then
            CType(lyr, FeatureLayer).UnSelectAll()
        End If
    End Sub

#End Region

#Region "Map Color Functions..."

    ''' <summary>
    ''' Return gradient color using 4-color transition
    ''' </summary>
    ''' <param name="Intensity">Number from 0.0 to 1.0 indicating desired relative value</param>
    ''' <param name="_Colors">Array of 5 colors associated with below threshold and smallest intensity to largest</param>
    ''' <param name="InvertColors">If true, swap order of colors</param>
    ''' <param name="MinThresholdValue">Min threshold intensity</param>
    ''' <param name="MaxThresholdValue">Max threshold intensity</param>
    Friend Shared Function GradientColor(Intensity As Single, _Colors() As Color, InvertColors As Boolean, MinThresholdValue As Double, MaxThresholdValue As Double) As Color
        Dim Colors() As Color = _Colors.Clone
        If InvertColors Then Array.Reverse(Colors, 1, 4) 'reverse the top 4 colors (not the non-threshold value)
        If Intensity < 0.33 Then
            GradientColor = ColorCombine(Colors(1), Colors(2), (Intensity - 0.0) / 0.33)
        ElseIf Intensity < 0.67 Then
            GradientColor = ColorCombine(Colors(2), Colors(3), (Intensity - 0.33) / 0.33)
        Else
            GradientColor = ColorCombine(Colors(3), Colors(4), (Intensity - 0.67) / 0.33)
        End If
        'If Intensity < MinThresholdValue Or Intensity > MaxThresholdValue Then GradientColor = Colors(0)
    End Function

    ''' <summary>
    ''' Return color associated with intensity using Animation defaults
    ''' </summary>
    ''' <param name="Intensity">Number from 0.0 to 1.0 indicating desired relative value</param>
    Friend Function GradientColor(ByVal Intensity As Single) As Color
        With AnimationInfo
            Return GradientColor(Intensity, .Colors, .InvertColor, (.MinThresholdValue - .MinValue) / (.MaxValue - .MinValue), (.MaxThresholdValue - .MinValue) / (.MaxValue - .MinValue))
        End With
    End Function

    Private Shared Function ColorCombine(ByVal Color1 As System.Drawing.Color, ByVal Color2 As System.Drawing.Color, ByVal Opacity As Double) As System.Drawing.Color
        Opacity = Math.Min(1, Math.Max(0, Opacity))
        Return Drawing.Color.FromArgb(GammaCombine(Color1.R, Color2.R, Opacity),
                                      GammaCombine(Color1.G, Color2.G, Opacity),
                                      GammaCombine(Color1.B, Color2.B, Opacity))
    End Function

    Private Shared Function GammaCombine(ByVal v1 As Integer, ByVal v2 As Integer, ByVal a As Double)
        Dim d1 As Double = v1 / 255.0
        Dim d2 As Double = v2 / 255.0
        Dim f As Double = 2.2
        Dim r As Integer = Int(Math.Pow(Math.Pow(d1, f) * (1 - a) + Math.Pow(d2, f) * a, 1 / f) * 255 + 0.5)
        Return Math.Min(255, r)
    End Function

    Private Function ColorToUInt(ByVal c As Color) As UInt32
        Return Convert.ToUInt32(System.Drawing.ColorTranslator.ToWin32(c))
    End Function

#End Region

    Private Sub CustomizeToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuCustomize.Click
        With New frmToolbar(tbMain)
            .ShowDialog(Me)
            .Dispose()
        End With
    End Sub

    ''' <summary>
    ''' Used to find the data row associated with the station that was just clicked on
    ''' </summary>
    ''' <param name="ScreenX">X Mouse click position in pixels</param>
    ''' <param name="ScreenY">Y Mouse click position in pixels</param>
    ''' <returns>Datarow containing station clicked on (or Nothing if didn't click close enough)</returns>
    Private Function FindShapes(ByVal ScreenX As Integer, ByVal ScreenY As Integer) As Generic.List(Of Integer)
        Dim lst As New Generic.List(Of Integer)
        Try
            Const WindowSize As Integer = 5

            With WRDBStationsLayer

                'following cache speeds up search--only computed when extents change
                If lstCoords.Count = 0 Then
                    IsCancelled = False
                    For r As Integer = 0 To .DataSet.Features.Count - 1
                        If IsCancelled Or .DataSet.Features(r) Is Nothing Then Return lst
                        If GISMap.ViewExtents.Intersects(.DataSet.Features(r).Envelope) Then 'only compute if in view extents, otherwise add bogus point
                            lstCoords.Add(GISMap.ProjToPixel(.DataSet.Features(r).Coordinates(0)))
                        Else
                            lstCoords.Add(New Drawing.Point(0, 0))
                        End If
                        If r Mod 1000 = 0 Then Application.DoEvents()
                    Next
                End If

                For r As Integer = 0 To Math.Min(lstCoords.Count, .DataSet.Features.Count) - 1
                    Dim p As Drawing.Point = lstCoords(r)
                    Dim typeMatches As Boolean = True
                    If ModeSettings.DisplayCoordsByType Then
                        Dim value As String = TestNull(.DataSet.DataTable.Rows(r)(enumField.Type), "")
                        typeMatches = (cboStationType.Text = value OrElse (cboStationType.SelectedIndex = 0 AndAlso cboStationType.Items.Contains(value)))
                    End If
                    If typeMatches AndAlso Math.Abs(ScreenX - p.X) < WindowSize AndAlso Math.Abs(ScreenY - p.Y) < WindowSize Then lst.Add(r)
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
        Return lst
    End Function

    Private Function Invert(ByVal clr As Color) As Color
        Return Color.FromArgb(Not clr.R, Not clr.G, clr.B)
    End Function

    ''' <summary>
    ''' Save drawing to .GIS or .DSPX file
    ''' </summary>
    ''' <param name="_FileName">Name of file to save to</param>
    ''' <param name="SaveAs">True if want to force prompt of filename</param>
    ''' <returns>True if successful</returns>
    ''' <remarks>now supports .dspx file which stores ALL MapWindow settings</remarks>
    Private Function SaveSettings(ByRef _FileName As String, Optional ByVal SaveAs As Boolean = False) As Boolean
        Try
            If SaveAs Or _FileName = "" Then
                With New SaveFileDialog
                    .AddExtension = True
                    .CheckPathExists = True
                    .CreatePrompt = False
                    .DefaultExt = ".gis"
                    .Filter = "WRDB GIS File|*.gis|MapWindow " & GISAppManager.SerializationManager.SaveDialogFilterText
                    .FilterIndex = 0
                    .OverwritePrompt = True
                    .Title = "Save GIS Project File"
                    .ValidateNames = True
                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                        _FileName = .FileName
                        ResetTitleBar()
                        .Dispose()
                    Else
                        .Dispose()
                        Return False
                    End If
                End With
            End If

            If My.Computer.FileSystem.FileExists(_FileName) Then My.Computer.FileSystem.DeleteFile(_FileName)

            If String.Equals(IO.Path.GetExtension(_FileName), ".dspx", StringComparison.InvariantCultureIgnoreCase) Then
                If SaveAs Then frmInfoMsg.ShowInfoMsg("Save as MapWindow File", "You can save the map information in a MapWindow-compatible .dspx format. This promises to have the highest fidelity for layer thematic mapping but currently suffers from the flaw that all layer files must reside in the same folder as the .dspx file. In addition, there are some stability and performance issues with this format.")
                With GISAppManager.SerializationManager
                    .SetCustomSetting(enumKey.GIS_XMin.ToString, GISMap.ViewExtents.MinX.ToString)
                    .SetCustomSetting(enumKey.GIS_XMax.ToString, GISMap.ViewExtents.MaxX.ToString)
                    .SetCustomSetting(enumKey.GIS_YMin.ToString, GISMap.ViewExtents.MinY.ToString)
                    .SetCustomSetting(enumKey.GIS_YMax.ToString, GISMap.ViewExtents.MaxY.ToString)
                    .SetCustomSetting(enumKey.GIS_Display.ToString, cboStatistic.Text)
                    .SetCustomSetting(enumKey.GIS_PCode.ToString, cboVariable.Text)
                    .SetCustomSetting(enumKey.GIS_Aggregate.ToString, Aggregate.ToString)
                    .SetCustomSetting(enumKey.GIS_DateTime.ToString, AnimationInfo.ThisDate.ToString)
                    .SetCustomSetting(enumKey.GIS_AnimSpeed.ToString, AnimationInfo.Speed.ToString)

                    .SetCustomSetting(enumKey.GIS_DataLayer.ToString, AnimationInfo.DataLayerName)
                    .SetCustomSetting(enumKey.GIS_DataAttribute.ToString, AnimationInfo.DataAttribute)
                    .SetCustomSetting(enumKey.GIS_DataField.ToString, AnimationInfo.DataField)
                    .SetCustomSetting(enumKey.GIS_DataLinkageType.ToString, AnimationInfo.DataLinkageType)
                    .SetCustomSetting(enumKey.GIS_DataVectors.ToString, AnimationInfo.DataShowVectors)
                    .SetCustomSetting(enumKey.GIS_DataXField.ToString, AnimationInfo.DataXParm)
                    .SetCustomSetting(enumKey.GIS_DataYField.ToString, AnimationInfo.DataYParm)
                    .SetCustomSetting(enumKey.GIS_DataArrowLength.ToString, AnimationInfo.DataArrowLength.ToString)
                    .SetCustomSetting(enumKey.GIS_DataArrowColor.ToString, AnimationInfo.DataArrowColor.Name)
                    .SetCustomSetting(enumKey.GIS_DataColor0.ToString, AnimationInfo.Colors(0).Name)
                    .SetCustomSetting(enumKey.GIS_DataColor1.ToString, AnimationInfo.Colors(1).Name)
                    .SetCustomSetting(enumKey.GIS_DataColor2.ToString, AnimationInfo.Colors(2).Name)
                    .SetCustomSetting(enumKey.GIS_DataColor3.ToString, AnimationInfo.Colors(3).Name)
                    .SetCustomSetting(enumKey.GIS_DataColor4.ToString, AnimationInfo.Colors(4).Name)
                    .SetCustomSetting(enumKey.GIS_DataInvert.ToString, AnimationInfo.InvertColor.ToString)
                    .SetCustomSetting(enumKey.GIS_DataMinThresholdValue.ToString, AnimationInfo.MinThresholdValue.ToString)
                    .SetCustomSetting(enumKey.GIS_DataMaxThresholdValue.ToString, AnimationInfo.MaxThresholdValue.ToString)
                    .SetCustomSetting(enumKey.GIS_NumCategories.ToString, AnimationInfo.NumCategories.ToString)
                    .SetCustomSetting(enumKey.GIS_ScalingOption.ToString, AnimationInfo.ScalingOption)

                    Dim FileName As String = ""
                    If AnimationInfo.dsData IsNot Nothing Then FileName = AnimationInfo.dsData.FileName
                    .SetCustomSetting(enumKey.GIS_DataFile.ToString, FileName)
                    .SetCustomSetting(enumKey.GIS_DataFileRelative.ToString, GetRelativePath(IO.Path.GetDirectoryName(_FileName), FileName))
                    .SaveProject(_FileName)
                End With
            Else
                'save general settings and last layers loaded

                If SaveAs Then frmInfoMsg.ShowInfoMsg("Save as GIS File", "You can save the map information in a WRDB GIS .gis format. This is the standard format but may not preserve all layer thematic mapping settings; however this format stores both the absolute and relative directories for each layer file to give you the most flexibility.")

                Dim IniFile As New IniFile.clsIniFile(_FileName, True)
                With IniFile
                    .SetKeyText("Settings", enumKey.GIS_XMin.ToString, GISMap.ViewExtents.MinX.ToString)
                    .SetKeyText("Settings", enumKey.GIS_XMax.ToString, GISMap.ViewExtents.MaxX.ToString)
                    .SetKeyText("Settings", enumKey.GIS_YMin.ToString, GISMap.ViewExtents.MinY.ToString)
                    .SetKeyText("Settings", enumKey.GIS_YMax.ToString, GISMap.ViewExtents.MaxY.ToString)
                    .SetKeyText("Settings", enumKey.GIS_Display.ToString, cboStatistic.Text)
                    .SetKeyText("Settings", enumKey.GIS_PCode.ToString, cboVariable.Text)
                    .SetKeyText("Settings", enumKey.GIS_Aggregate.ToString, Aggregate.ToString)
                    .SetKeyText("Settings", enumKey.GIS_DateTime.ToString, AnimationInfo.ThisDate.ToString)
                    .SetKeyText("Settings", enumKey.GIS_AnimSpeed.ToString, AnimationInfo.Speed.ToString)

                    .SetKeyText("Settings", enumKey.GIS_DataLayer.ToString, AnimationInfo.DataLayerName)
                    .SetKeyText("Settings", enumKey.GIS_DataAttribute.ToString, AnimationInfo.DataAttribute)
                    .SetKeyText("Settings", enumKey.GIS_DataLinkageType.ToString, AnimationInfo.DataLinkageType)
                    .SetKeyText("Settings", enumKey.GIS_DataVectors.ToString, AnimationInfo.DataShowVectors)
                    .SetKeyText("Settings", enumKey.GIS_DataXField.ToString, AnimationInfo.DataXParm)
                    .SetKeyText("Settings", enumKey.GIS_DataYField.ToString, AnimationInfo.DataYParm)
                    .SetKeyText("Settings", enumKey.GIS_DataArrowLength.ToString, AnimationInfo.DataArrowLength)
                    .SetKeyText("Settings", enumKey.GIS_DataArrowColor.ToString, AnimationInfo.DataArrowColor.Name)
                    .SetKeyText("Settings", enumKey.GIS_DataColor0.ToString, AnimationInfo.Colors(0).Name)
                    .SetKeyText("Settings", enumKey.GIS_DataColor1.ToString, AnimationInfo.Colors(1).Name)
                    .SetKeyText("Settings", enumKey.GIS_DataColor2.ToString, AnimationInfo.Colors(2).Name)
                    .SetKeyText("Settings", enumKey.GIS_DataColor3.ToString, AnimationInfo.Colors(3).Name)
                    .SetKeyText("Settings", enumKey.GIS_DataColor4.ToString, AnimationInfo.Colors(4).Name)
                    .SetKeyText("Settings", enumKey.GIS_DataInvert.ToString, AnimationInfo.InvertColor.ToString)
                    .SetKeyText("Settings", enumKey.GIS_DataMinThresholdValue.ToString, AnimationInfo.MinThresholdValue.ToString)
                    .SetKeyText("Settings", enumKey.GIS_DataMaxThresholdValue.ToString, AnimationInfo.MaxThresholdValue.ToString)
                    .SetKeyText("Settings", enumKey.GIS_NumCategories.ToString, AnimationInfo.NumCategories.ToString)
                    .SetKeyText("Settings", enumKey.GIS_ScalingOption.ToString, AnimationInfo.ScalingOption)
                    .SetKeyText("Settings", enumKey.GIS_OverrideScheme.ToString, AnimationInfo.OverrideScheme)
                    For Each t In AnimationInfo.CustomScheme
                        .SetKeyText("CustomScheme", String.Format("{0:0.000}", t.Item1), t.Item2.Name)
                    Next

                    Dim FileName As String = ""
                    If AnimationInfo.dsData IsNot Nothing Then FileName = AnimationInfo.dsData.FileName
                    .SetKeyText("Settings", enumKey.GIS_DataFile.ToString, FileName)
                    .SetKeyText("Settings", enumKey.GIS_DataFileRelative.ToString, GetRelativePath(IO.Path.GetDirectoryName(.FileName), FileName))

                    .SetKeyText("Settings", enumKey.Projection.ToString, ProjectProjection.ToEsriString)

                    'note: webmaps sometimes uses hidden layer; remove it here
                    Dim l As Integer = 0
                    For Each lyr In GISMap.GetLayers
                        If lyr.LegendItemVisible Then
                            SaveLayer(IniFile, lyr, "Layer_" & l)
                            l += 1
                        End If
                    Next

                    .SetKeyText("Settings", enumKey.NumLayers.ToString, l)

                    .Save()
                End With
            End If

            IsModified = False
            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        End Try
    End Function

    Friend Sub SetBasemap(ServiceProviderName As String)
        Try

            Application.DoEvents()

            If ServiceProviderName <> "None" Then
                'before reprojecting, need to make sure there are not any point layers with a single point
                '(because the library will throw overflow errors)
                For Each lyr As IMapLayer In GISMap.GetAllLayers
                    If lyr.Extent.Width = 0 Or lyr.Extent.Height = 0 Then
                        WarningMsg("Due to a limitation in the GIS library, you cannot add a basemap when any existing layers have a zero width or height.")
                        Exit Sub
                    End If
                Next

                'before reprojecting, need to make sure there are not any raster or image layers already loaded that are not using the WebMercator projection 
                '(because the library will try to reproject and that feature is not working properly and will cause errors)
                SetBasemap("None")
                For Each lyr As IMapLayer In GISMap.GetAllLayers
                    Select Case LayerType(lyr)
                        Case enumLayerType.Image, enumLayerType.Raster, enumLayerType.TiledImage
                            If Not lyr.Projection.Equals(DotSpatial.Projections.KnownCoordinateSystems.Projected.World.WebMercator) Then
                                WarningMsg("One or more raster layers currently loaded are not using the Web Mercator projection which is required for viewing basemaps. Although vector layers are automatically reprojected, at this time is it not possible to automatically reproject raster layers. If you wish to view raster layers with a web basemap layer, you must first manually reproject the raster layers using Tools | Reproject Layer; select Projected | World | WebMercator.")
                                Exit Sub
                            End If
                    End Select
                Next
            End If
            For i As Integer = 2 To mnuLayerBasemap.DropDownItems.Count - 1
                With CType(mnuLayerBasemap.DropDownItems(i), ToolStripMenuItem)
                    .Checked = .Text = ServiceProviderName
                End With
            Next
            GISMap.Enabled = False
            cboLayerBasemap.Text = ServiceProviderName
            WebMap.ServiceProviderName = ServiceProviderName
            ProjectProjection = GISMap.Projection 'gets reset in WebMap
            SetExtentsAndReproject()

        Catch ex As Exception

            ErrorMsg(, ex)

        Finally

            GISMap.Enabled = True

        End Try

    End Sub

    ''' <summary>
    ''' Load settings from specified .GIS file (now supports .dspx file which stores ALL MapWindow settings)
    ''' </summary>
    Private Sub LoadSettings(ByRef _FileName As String, IsAppend As Boolean)
        Try
            If Not My.Computer.FileSystem.FileExists(_FileName) Then Exit Sub

            IsCancelled = False

            If Not IsAppend Then
                WRDBStationsLayer = Nothing
                SetBasemap("None")
                GISMap.Layers.Clear()
                If AnimationInfo.dsData IsNot Nothing Then AnimationInfo.dsData.Dispose() : AnimationInfo.dsData = Nothing
                If dtResults IsNot Nothing Then dtResults.Clear() : dtResults.Dispose() : dtResults = Nothing
                'tblData.Visible = False
            End If

            GC.Collect()

            GISMap.Enabled = False
            GISMap.SuspendLayout()
            GISMap.Layers.SuspendEvents()
            Dim ext As DotSpatial.Data.Extent = GISMap.ViewExtents

            Dim StaColor As Drawing.Color = Color.Red, StaType As DotSpatial.Symbology.PointShape = DotSpatial.Symbology.PointShape.Ellipse

            If String.Equals(IO.Path.GetExtension(_FileName), ".dspx", StringComparison.InvariantCultureIgnoreCase) Then

                'note: ideally, this would be the easiest way to go, however there are a few problems with this format:
                ' 1. get error message if legend text of category is ***
                ' 2. will not load any shape files unless they are in the same folder as the dspx file
                ' 3. blocks UI during load
                ' 4. is really slow

                WriteProgress("Loading map from: {0}...", _FileName)

                With GISAppManager.SerializationManager
                    .OpenProject(_FileName)

                    Dim xmin, xmax, ymin, ymax As Double
                    xmin = Val(.GetCustomSetting(enumKey.GIS_XMin.ToString, "-1"))
                    xmax = Val(.GetCustomSetting(enumKey.GIS_XMax.ToString, "-1"))
                    ymin = Val(.GetCustomSetting(enumKey.GIS_YMin.ToString, "-1"))
                    ymax = Val(.GetCustomSetting(enumKey.GIS_YMax.ToString, "-1"))
                    If xmin <> -1 Then ext.SetValues(xmin, ymin, xmax, ymax)

                    AnimationInfo.DataLayerName = .GetCustomSetting(enumKey.GIS_DataLayer.ToString, "")
                    AnimationInfo.DataAttribute = .GetCustomSetting(enumKey.GIS_DataAttribute.ToString, "")
                    AnimationInfo.DataField = .GetCustomSetting(enumKey.GIS_DataField.ToString, "")
                    AnimationInfo.DataLinkageType = .GetCustomSetting(enumKey.GIS_DataLinkageType.ToString, enumLinkageType.StationID)
                    AnimationInfo.DataShowVectors = .GetCustomSetting(enumKey.GIS_DataVectors.ToString, False)
                    AnimationInfo.DataXParm = .GetCustomSetting(enumKey.GIS_DataXField.ToString, "")
                    AnimationInfo.DataYParm = .GetCustomSetting(enumKey.GIS_DataYField.ToString, "")
                    AnimationInfo.DataArrowLength = Val(.GetCustomSetting(enumKey.GIS_DataArrowLength.ToString, "0"))
                    AnimationInfo.DataArrowColor = Color.FromName(.GetCustomSetting(enumKey.GIS_DataArrowColor.ToString, Color.Black.Name))
                    Dim DataFile As String = .GetCustomSetting(enumKey.GIS_DataFile.ToString, "")
                    Dim DataFileRelative As String = .GetCustomSetting(enumKey.GIS_DataFileRelative.ToString, "")
                    Dim lyrFound As Boolean = False
                    For i As Integer = 0 To GISMap.Layers.Count - 1
                        If GISMap.Layers(i).LegendText IsNot Nothing AndAlso GISMap.Layers(i).LegendText.Equals(AnimationInfo.DataLayerName, StringComparison.OrdinalIgnoreCase) Then lyrFound = True : Exit For
                    Next
                    If Not lyrFound Then AnimationInfo.DataLayerName = ""
                    If Mode = enumMode.Animate AndAlso DataFile <> "" AndAlso AnimationInfo.DataLayerName <> "" AndAlso AnimationInfo.DataAttribute <> "" AndAlso FindFile(DataFile, IO.Path.GetDirectoryName(_FileName), DataFileRelative) <> "" Then
                        If Not AssignData(DataFile, AnimationInfo.DataLayerName, AnimationInfo.DataAttribute, AnimationInfo.DataLinkageType,
                                   AnimationInfo.DataShowVectors, AnimationInfo.DataXParm, AnimationInfo.DataYParm, AnimationInfo.DataArrowLength, AnimationInfo.DataArrowColor) Then Exit Sub
                        If IsCancelled Then Exit Sub
                    End If
                    AnimationInfo.Colors(0) = Color.FromName(.GetCustomSetting(enumKey.GIS_DataColor0.ToString, Color.LightGray.Name))
                    AnimationInfo.Colors(1) = Color.FromName(.GetCustomSetting(enumKey.GIS_DataColor1.ToString, Color.Green.Name))
                    AnimationInfo.Colors(2) = Color.FromName(.GetCustomSetting(enumKey.GIS_DataColor2.ToString, Color.Yellow.Name))
                    AnimationInfo.Colors(3) = Color.FromName(.GetCustomSetting(enumKey.GIS_DataColor3.ToString, Color.Red.Name))
                    AnimationInfo.Colors(4) = Color.FromName(.GetCustomSetting(enumKey.GIS_DataColor4.ToString, Color.White.Name))
                    AnimationInfo.InvertColor = .GetCustomSetting(enumKey.GIS_DataInvert.ToString, "False")
                    AnimationInfo.MinThresholdValue = .GetCustomSetting(enumKey.GIS_DataMinThresholdValue.ToString, Integer.MinValue)
                    AnimationInfo.MaxThresholdValue = .GetCustomSetting(enumKey.GIS_DataMaxThresholdValue.ToString, Integer.MaxValue)
                    AnimationInfo.NumCategories = .GetCustomSetting(enumKey.GIS_NumCategories.ToString, 50)
                    AnimationInfo.ScalingOption = .GetCustomSetting(enumKey.GIS_ScalingOption.ToString, enumScalingOption.DataRange)
                    cboStatistic.Text = .GetCustomSetting(enumKey.GIS_Display.ToString, "Average Values")
                    CheckDropDownItem(mnuDataStatistic, cboStatistic.SelectedIndex)

                    cboAggregation.SelectedIndex = [Enum].Parse(GetType(enumAggregate), .GetCustomSetting(enumKey.GIS_Aggregate.ToString, enumAggregate.None.ToString))

                    Dim PCode As String = .GetCustomSetting(enumKey.GIS_PCode.ToString, "")
                    If cboVariable.Items.Contains(PCode) Then
                        cboVariable.Text = PCode
                    Else
                        cboVariable.Text = PCode.Split(" ")(0) 'may have pcode description attached
                    End If

                    CheckDropDownItem(mnuDataVariable, cboVariable.SelectedIndex)

                    tblData.Visible = cboAggregation.Visible AndAlso Aggregate <> enumAggregate.POR
                    mnuViewAnimate.Enabled = cboAggregation.SelectedIndex <> 0
                    mnuViewAnimateSpeed.Enabled = cboAggregation.SelectedIndex <> 0
                    CheckDropDownItem(mnuDataAggregation, cboAggregation.SelectedIndex)
                    ResetAggregate()

                    AnimationInfo.ThisDate = CDate(.GetCustomSetting(enumKey.GIS_DateTime.ToString, Date.MinValue.ToString))
                    If AnimationInfo.ThisDate = Date.MinValue Then AnimationInfo.ThisDate = AnimationInfo.MinDate
                    AnimationInfo.Speed = .GetCustomSetting(enumKey.GIS_AnimSpeed.ToString, "3")

                    .StartTrackingChanges()
                End With
            Else
                Dim IniFile As New IniFile.clsIniFile(_FileName, False)

                With IniFile
                    Dim EsriString As String = .GetKeyText("Settings", enumKey.Projection.ToString, "")
                    If ProjectProjection.TryParseEsriString(EsriString) Then
                        ProjectProjection = DotSpatial.Projections.ProjectionInfo.FromEsriString(EsriString)
                    Else
                        ProjectProjection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
                    End If
                    Dim NumLayers As Integer = Val(.GetKeyText("Settings", enumKey.NumLayers.ToString, "0"))

                    For i As Integer = 0 To NumLayers - 1
                        LoadLayer(IniFile, GISMap.MapFrame, "Layer_" & i)
                        If IsCancelled Then Exit For
                    Next

                    If IniFile IsNot Nothing Then .Save()

                    Dim xmin, xmax, ymin, ymax As Double
                    xmin = Val(.GetKeyText("Settings", enumKey.GIS_XMin.ToString, "-1"))
                    xmax = Val(.GetKeyText("Settings", enumKey.GIS_XMax.ToString, "-1"))
                    ymin = Val(.GetKeyText("Settings", enumKey.GIS_YMin.ToString, "-1"))
                    ymax = Val(.GetKeyText("Settings", enumKey.GIS_YMax.ToString, "-1"))
                    If xmin <> -1 Then ext.SetValues(xmin, ymin, xmax, ymax)

                    AnimationInfo.DataLayerName = .GetKeyText("Settings", enumKey.GIS_DataLayer.ToString, "")
                    AnimationInfo.DataAttribute = .GetKeyText("Settings", enumKey.GIS_DataAttribute.ToString, "")
                    AnimationInfo.DataField = .GetKeyText("Settings", enumKey.GIS_DataField.ToString, "")
                    AnimationInfo.DataLinkageType = .GetKeyText("Settings", enumKey.GIS_DataLinkageType.ToString, enumLinkageType.StationID)
                    AnimationInfo.DataShowVectors = .GetKeyText("Settings", enumKey.GIS_DataVectors.ToString, False)
                    AnimationInfo.DataXParm = .GetKeyText("Settings", enumKey.GIS_DataXField.ToString, "")
                    AnimationInfo.DataYParm = .GetKeyText("Settings", enumKey.GIS_DataYField.ToString, "")
                    AnimationInfo.DataArrowLength = .GetKeyText("Settings", enumKey.GIS_DataArrowLength.ToString, 0)
                    Dim DataFile As String = .GetKeyText("Settings", enumKey.GIS_DataFile.ToString, "")
                    Dim DataFileRelative As String = .GetKeyText("Settings", enumKey.GIS_DataFileRelative.ToString, "")
                    Dim lyrFound As Boolean = False
                    For i As Integer = 0 To GISMap.Layers.Count - 1
                        If CStr(TestNull(GISMap.Layers(i).LegendText, "")).Equals(AnimationInfo.DataLayerName, StringComparison.OrdinalIgnoreCase) Then lyrFound = True : Exit For
                    Next
                    If Not lyrFound Then AnimationInfo.DataLayerName = ""
                    If Mode = enumMode.Animate AndAlso DataFile <> "" AndAlso AnimationInfo.DataLayerName <> "" AndAlso FindFile(DataFile, IO.Path.GetDirectoryName(.FileName), DataFileRelative) <> "" Then
                        If Not AssignData(DataFile, AnimationInfo.DataLayerName, AnimationInfo.DataAttribute, AnimationInfo.DataLinkageType,
                                   AnimationInfo.DataShowVectors, AnimationInfo.DataXParm, AnimationInfo.DataYParm, AnimationInfo.DataArrowLength, AnimationInfo.DataArrowColor, False) Then Exit Sub
                        If IsCancelled Then Exit Sub
                    End If
                    AnimationInfo.Colors(0) = Color.FromName(.GetKeyText("Settings", enumKey.GIS_DataColor0.ToString, Color.LightGray.Name))
                    AnimationInfo.Colors(1) = Color.FromName(.GetKeyText("Settings", enumKey.GIS_DataColor1.ToString, Color.Green.Name))
                    AnimationInfo.Colors(2) = Color.FromName(.GetKeyText("Settings", enumKey.GIS_DataColor2.ToString, Color.Yellow.Name))
                    AnimationInfo.Colors(3) = Color.FromName(.GetKeyText("Settings", enumKey.GIS_DataColor3.ToString, Color.Red.Name))
                    AnimationInfo.Colors(4) = Color.FromName(.GetKeyText("Settings", enumKey.GIS_DataColor4.ToString, Color.White.Name))
                    AnimationInfo.InvertColor = .GetKeyText("Settings", enumKey.GIS_DataInvert.ToString, "False")
                    AnimationInfo.MinThresholdValue = .GetKeyText("Settings", enumKey.GIS_DataMinThresholdValue.ToString, Integer.MinValue)
                    AnimationInfo.MaxThresholdValue = .GetKeyText("Settings", enumKey.GIS_DataMaxThresholdValue.ToString, Integer.MaxValue)
                    AnimationInfo.NumCategories = .GetKeyText("Settings", enumKey.GIS_NumCategories.ToString, 50)
                    AnimationInfo.ScalingOption = .GetKeyText("Settings", enumKey.GIS_ScalingOption.ToString, enumScalingOption.DataRange)

                    AnimationInfo.OverrideScheme = .GetKeyText("Settings", enumKey.GIS_OverrideScheme.ToString, False)
                    AnimationInfo.CustomScheme.Clear()
                    For Each k As String In .GetKeyNames("CustomScheme")
                        AnimationInfo.CustomScheme.Add(Tuple.Create(CDbl(k), Color.FromName(.GetKeyText("CustomScheme", k, Color.White.Name))))
                    Next

                    SuspendMapUpdate = True

                    If GISMap.Layers.Count > 0 Then GISMap.Layers.SelectedLayer = GISMap.Layers(0)

                    cboStatistic.Text = .GetKeyText("Settings", enumKey.GIS_Display.ToString, "Average Values")
                    CheckDropDownItem(mnuDataStatistic, cboStatistic.SelectedIndex)

                    'cboAggregation.SelectedIndex = [Enum].Parse(GetType(enumAggregate), .GetKeyText("Settings", enumKey.GIS_Aggregate.ToString, enumAggregate.None.ToString))

                    Dim PCode As String = .GetKeyText("Settings", enumKey.GIS_PCode.ToString, "")
                    If cboVariable.Items.Contains(PCode) Then
                        cboVariable.Text = PCode
                    Else
                        cboVariable.Text = PCode.Split(" ")(0) 'may have pcode description attached
                    End If
                    CheckDropDownItem(mnuDataVariable, cboVariable.SelectedIndex)

                    'tblData.Visible = cboAggregation.Visible AndAlso Aggregate <> enumAggregate.POR
                    mnuViewAnimate.Enabled = cboAggregation.SelectedIndex <> 0
                    mnuViewAnimateSpeed.Enabled = cboAggregation.SelectedIndex <> 0
                    CheckDropDownItem(mnuDataAggregation, cboAggregation.SelectedIndex)
                    If cboAggregation.Visible Then ResetAggregate()

                    AnimationInfo.ThisDate = CDate(.GetKeyText("Settings", enumKey.GIS_DateTime.ToString, Date.MinValue.ToString))
                    If AnimationInfo.ThisDate = Date.MinValue Then AnimationInfo.ThisDate = AnimationInfo.MinDate
                    AnimationInfo.Speed = .GetKeyText("Settings", enumKey.GIS_AnimSpeed.ToString, "3")

                End With
            End If

            'find WRDB Stations layer and always put it on top (last in layer list)
            moveLayerTop(GetLayer(WRDBStationsLayerName))

            GISMap.Layers.ResumeEvents()

            SuspendMapUpdate = False
            GISMap.Invalidate()

            'for some reason, after use OpenProject, legend doesn't refresh properly; must detach then reattach

            With GISLegend
                .RootNodes.Clear()
                .AddMapFrame(GISMap.MapFrame)
            End With

            If GISMap.Layers.Count > 0 Then SelectLayer(GISMap.Layers(0)) 'this forces it to be selected in the legend

            'collapse all layers
            mnuLayerCollapseAll.PerformClick()

            SummarizeStationData()
            ResetAnimation()
            RefreshStationsDisplay()

            WebMap.Refresh()

            GISMap.ViewExtents = ext

            WebMap.Refresh()
            GISMap.Refresh()

        Catch ex As Exception
            ErrorMsg("An error occurred while trying to read the .GIS file.", ex)
        Finally
            GISMap.Enabled = True
            GISMap.ResumeLayout()
            IsModified = False
            ProgressComplete()
        End Try
    End Sub

    ''' <summary>
    ''' Save layer information to ini file
    ''' </summary>
    Friend Sub SaveLayer(IniFile As IniFile.clsIniFile, Layer As IMapLayer, Section As String)
        With Layer

            Dim FileName As String = ""
            Dim Symbolizer As ILegendItem = Nothing
            Dim Scheme As IFeatureScheme = Nothing

            Select Case LayerType(Layer)
                Case enumLayerType.Image, enumLayerType.Line, enumLayerType.Point, enumLayerType.Polygon, enumLayerType.Raster, enumLayerType.TiledImage, enumLayerType.Group
                    IniFile.SetKeyText(Section, enumKey.LegendText.ToString, .LegendText)
                    IniFile.SetKeyText(Section, enumKey.Visible.ToString, .IsVisible.ToString)
                    Select Case LayerType(Layer)
                        Case enumLayerType.Group
                            With CType(Layer, MapGroup)
                                IniFile.SetKeyText(Section, enumKey.NumLayers.ToString, .GetLayers.Count)
                                For LayerNum As Integer = 0 To .GetLayers.Count - 1
                                    SaveLayer(IniFile, .GetLayers(LayerNum), Section & "_" & LayerNum + 1)
                                Next
                            End With
                        Case enumLayerType.Line, enumLayerType.Point, enumLayerType.Polygon
                            With CType(Layer, FeatureLayer)
                                FileName = .DataSet.Filename
                                Symbolizer = .Symbolizer
                                Scheme = .Symbology
                            End With
                        Case enumLayerType.Raster
                            With CType(Layer, RasterLayer)
                                FileName = .DataSet.Filename
                                Symbolizer = .Symbolizer
                            End With
                        Case enumLayerType.Image
                            With CType(Layer, ImageLayer)
                                FileName = .DataSet.Filename
                                Symbolizer = .Symbolizer
                                If String.IsNullOrEmpty(FileName) AndAlso Layer.LegendText = "Online Basemap" Then
                                    For Each mnuItem As ToolStripItem In mnuLayerBasemap.DropDownItems
                                        If TypeOf mnuItem Is ToolStripMenuItem AndAlso CType(mnuItem, ToolStripMenuItem).Checked Then
                                            IniFile.SetKeyText(Section, enumKey.Basemap.ToString, mnuItem.Text)
                                            Exit For
                                        End If
                                    Next
                                    Exit Sub
                                End If
                            End With
                    End Select
                Case Else
                    Exit Sub
            End Select

            If Layer.LegendType = LegendType.Layer AndAlso Not String.IsNullOrEmpty(FileName) Then

                IniFile.SetKeyText(Section, enumKey.ShapeFile.ToString, FileName)
                IniFile.SetKeyText(Section, enumKey.ShapeFileRelative.ToString, GetRelativePath(IO.Path.GetDirectoryName(IniFile.FileName), FileName)) 'path relative to ini file

                If TypeOf Layer Is FeatureLayer Then 'is shapefile symbology
                    With Scheme
                        IniFile.SetKeyText(Section, enumKey.ThemeLegendText.ToString, .LegendText)
                        IniFile.SetKeyText(Section, enumKey.AppearsInLegend.ToString, .AppearsInLegend)
                        IniFile.SetKeyText(Section, enumKey.NumCategories.ToString, .NumCategories)
                        Dim i As Integer = 0
                        For Each cat As FeatureCategory In .GetCategories
                            i += 1
                            Dim SectionCat As String = Section & "_Cat_" & i
                            With cat
                                IniFile.SetKeyText(SectionCat, enumKey.FilterExpression.ToString, .FilterExpression)
                                IniFile.SetKeyText(SectionCat, enumKey.LegendText.ToString, .LegendText)
                                IniFile.SetKeyText(SectionCat, enumKey.LegendItemVisible.ToString, .LegendItemVisible)

                                If TypeOf Layer Is MapLineLayer Then
                                    With CType(.Symbolizer, LineSymbolizer)
                                        IniFile.SetKeyText(SectionCat, enumKey.LineColor.ToString, Drawing.ColorTranslator.ToWin32(.GetFillColor))
                                        IniFile.SetKeyText(SectionCat, enumKey.LineWidth.ToString, .GetWidth.ToString)
                                        If .Strokes.Count > 0 AndAlso TypeOf .Strokes(0) Is SimpleStroke Then
                                            IniFile.SetKeyText(SectionCat, enumKey.DashStyle.ToString, CType(.Strokes(0), SimpleStroke).DashStyle)
                                        End If
                                    End With

                                ElseIf TypeOf Layer Is MapPolygonLayer Then
                                    With CType(.Symbolizer, PolygonSymbolizer)
                                        IniFile.SetKeyText(SectionCat, enumKey.LineColor.ToString, Drawing.ColorTranslator.ToWin32(.OutlineSymbolizer.GetFillColor))
                                        IniFile.SetKeyText(SectionCat, enumKey.LineWidth.ToString, .OutlineSymbolizer.GetWidth.ToString)
                                        IniFile.SetKeyText(SectionCat, enumKey.DashStyle.ToString, .OutlineSymbolizer.Strokes(0).ToPen(.OutlineSymbolizer.GetWidth).DashStyle)
                                        If .Patterns.Count > 0 Then
                                            If TypeOf .Patterns(0) Is GradientPattern Then
                                                IniFile.SetKeyText(SectionCat, enumKey.PatternType.ToString, "Gradient")
                                                With CType(.Patterns(0), GradientPattern)
                                                    IniFile.SetKeyText(SectionCat, enumKey.GradientType.ToString, .GradientType)
                                                    IniFile.SetKeyText(SectionCat, enumKey.GradientAngle.ToString, .Angle)
                                                    IniFile.SetKeyText(SectionCat, enumKey.NumGradientColors.ToString, .Colors.Count)
                                                    For j As Integer = 0 To .Colors.Count - 1
                                                        IniFile.SetKeyText(SectionCat, enumKey.FillColor.ToString & "_" & j + 1, Drawing.ColorTranslator.ToWin32(.Colors(j)))
                                                        IniFile.SetKeyText(SectionCat, enumKey.Position.ToString & "_" & j + 1, .Positions(j))
                                                    Next
                                                    IniFile.SetKeyText(SectionCat, enumKey.UseOutline.ToString, .UseOutline)
                                                End With
                                            ElseIf TypeOf .Patterns(0) Is SimplePattern Then
                                                IniFile.SetKeyText(SectionCat, enumKey.PatternType.ToString, "Simple")
                                                With CType(.Patterns(0), SimplePattern)
                                                    IniFile.SetKeyText(SectionCat, enumKey.FillColor.ToString, Drawing.ColorTranslator.ToWin32(.FillColor))
                                                    IniFile.SetKeyText(SectionCat, enumKey.Opacity.ToString, .Opacity)
                                                    IniFile.SetKeyText(SectionCat, enumKey.UseOutline.ToString, .UseOutline)
                                                End With
                                            ElseIf TypeOf .Patterns(0) Is HatchPattern Then
                                                IniFile.SetKeyText(SectionCat, enumKey.PatternType.ToString, "Hatch")
                                                With CType(.Patterns(0), HatchPattern)
                                                    IniFile.SetKeyText(SectionCat, enumKey.HatchStyle.ToString, .HatchStyle)
                                                    IniFile.SetKeyText(SectionCat, enumKey.ForeColor.ToString, Drawing.ColorTranslator.ToWin32(.ForeColor))
                                                    IniFile.SetKeyText(SectionCat, enumKey.BackColor.ToString, Drawing.ColorTranslator.ToWin32(.BackColor))
                                                    IniFile.SetKeyText(SectionCat, enumKey.Opacity.ToString, .ForeColorOpacity)
                                                    IniFile.SetKeyText(SectionCat, enumKey.UseOutline.ToString, .UseOutline)
                                                End With
                                            End If
                                        End If
                                    End With

                                ElseIf TypeOf Layer Is MapPointLayer Then
                                    Dim s As PointSymbolizer = .Symbolizer
                                    With s
                                        If .Symbols.Count > 0 AndAlso TypeOf .Symbols(0) Is SimpleSymbol Then
                                            With CType(s.Symbols(0), SimpleSymbol)
                                                IniFile.SetKeyText(SectionCat, enumKey.PointShape.ToString, .PointShape)
                                                IniFile.SetKeyText(SectionCat, enumKey.PointColor.ToString, Drawing.ColorTranslator.ToWin32(.Color))
                                                IniFile.SetKeyText(SectionCat, enumKey.PointSize.ToString, .Size.Width)
                                                IniFile.SetKeyText(SectionCat, enumKey.OutlineColor.ToString, Drawing.ColorTranslator.ToWin32(.OutlineColor))
                                                IniFile.SetKeyText(SectionCat, enumKey.UseOutline.ToString, .UseOutline)
                                                IniFile.SetKeyText(SectionCat, enumKey.PointAngle.ToString, .Angle.ToString)
                                            End With
                                        End If
                                    End With
                                End If
                            End With
                        Next cat
                    End With

                ElseIf TypeOf Layer Is RasterLayer Then

                    If Symbolizer IsNot Nothing Then
                        With CType(Symbolizer, RasterSymbolizer)

                            IniFile.SetKeyText(Section, enumKey.AppearsInLegend.ToString, .LegendItemVisible)
                            IniFile.SetKeyText(Section, enumKey.NumCategories.ToString, .Scheme.Categories.Count)
                            IniFile.SetKeyText(Section, enumKey.ThemeLegendText.ToString, .LegendText)
                            Dim i As Integer = 0
                            For Each cat As ColorCategory In .Scheme.Categories
                                i += 1
                                Dim SectionCat As String = Section & "_Cat_" & i
                                With cat
                                    IniFile.SetKeyText(SectionCat, enumKey.MinValue.ToString, cat.Minimum)
                                    IniFile.SetKeyText(SectionCat, enumKey.MaxValue.ToString, cat.Maximum)
                                    IniFile.SetKeyText(SectionCat, enumKey.LowColor.ToString, Drawing.ColorTranslator.ToWin32(cat.LowColor))
                                    IniFile.SetKeyText(SectionCat, enumKey.HighColor.ToString, Drawing.ColorTranslator.ToWin32(cat.HighColor))
                                    IniFile.SetKeyText(SectionCat, enumKey.LegendText.ToString, cat.LegendText)
                                End With
                            Next cat
                            IniFile.SetKeyText(Section, enumKey.NoDataColor.ToString, Drawing.ColorTranslator.ToWin32(.NoDataColor))
                        End With
                    End If

                ElseIf TypeOf Layer Is ImageLayer Then

                    If Symbolizer IsNot Nothing Then
                        With CType(Symbolizer, ImageSymbolizer)
                            'there are no properties to access here!
                        End With
                    End If
                End If
            End If
        End With
    End Sub

    ''' <summary>
    ''' Load layer information from ini file
    ''' </summary>
    Friend Sub LoadLayer(IniFile As IniFile.clsIniFile, MapGroup As IMapGroup, Section As String)

        Try

            GISMap.Layers.SuspendEvents()

            Dim LegendText As String = IniFile.GetKeyText(Section, enumKey.LegendText.ToString, "")
            Dim IsVisible As Boolean = IniFile.GetKeyText(Section, enumKey.Visible.ToString, True)

            Dim FileName As String = ""

            Dim FullFilename As String = IniFile.GetKeyText(Section, enumKey.ShapeFile.ToString, "")
            Dim RelFilename As String = IniFile.GetKeyText(Section, enumKey.ShapeFileRelative.ToString, "") 'path relative to ini file

            Dim Layer As IMapLayer = Nothing

            If FullFilename = "" And RelFilename = "" Then 'must be a basemap or layer group

                Dim Basemap As String = IniFile.GetKeyText(Section, enumKey.Basemap.ToString, "")

                If String.IsNullOrEmpty(Basemap) Then  'is mapgroup

                    Layer = New MapGroup(GISMap, LegendText)
                    Layer.LegendText = LegendText

                    Dim NumLayers As Integer = Val(IniFile.GetKeyText(Section, enumKey.NumLayers.ToString, "0"))

                    For i As Integer = 1 To NumLayers
                        LoadLayer(IniFile, Layer, Section & "_" & i)
                        If IsCancelled Then Exit Sub
                    Next

                Else

                    SetBasemap(Basemap)
                    Exit Sub

                End If


            Else 'load .shp file 

                WriteProgress("Loading layer from file: {0}", IO.Path.GetFileNameWithoutExtension(FullFilename))

                'see if file exists at absolute location
                If My.Computer.FileSystem.FileExists(FullFilename) Then
                    FileName = FullFilename
                Else 'see if is in relative location
                    Dim s As String = ""
                    s = IO.Path.Combine(IO.Path.GetDirectoryName(IniFile.FileName), RelFilename)
                    If My.Computer.FileSystem.FileExists(s) Then
                        FileName = s
                    Else 'see if is in same folder as .gis file
                        s = IO.Path.Combine(IO.Path.GetDirectoryName(IniFile.FileName), IO.Path.GetFileName(RelFilename))
                        If My.Computer.FileSystem.FileExists(s) Then
                            FileName = s
                        End If
                    End If
                End If
                If FileName = "" Then
                    WarningMsg("Unable to find layer file at either {0}, {1}, or {2}; layer will be skipped.", FullFilename, RelFilename, IO.Path.GetFileName(RelFilename))
                    Exit Sub
                End If

                'now add layer to parent group or map

                Layer = MapGroup.Layers.Add(FileName)

                With Layer

                    Dim Symbolizer As LegendItem = Nothing
                    Dim Scheme As IFeatureScheme = Nothing
                    Dim SelectionSymbolizer As IFeatureSymbolizer = Nothing

                    Select Case LayerType(Layer)
                        Case enumLayerType.Line, enumLayerType.Point, enumLayerType.Polygon
                            With CType(Layer, FeatureLayer)
                                FileName = .DataSet.Filename

                                'if WRDBStations layer already loaded, do not load again (in case specified basemap file with a stations layer already)
                                If WRDBStationsLayer IsNot Nothing AndAlso IO.Path.GetFileNameWithoutExtension(FileName).ToUpper = WRDBFileName.ToUpper Then
                                    MapGroup.Layers.Remove(Layer)
                                    Exit Sub
                                End If

                                Scheme = .Symbology
                                SelectionSymbolizer = .SelectionSymbolizer.Clone
                                Select Case LayerType(Layer)
                                    Case enumLayerType.Line : Scheme = New LineScheme
                                    Case enumLayerType.Point : Scheme = New PointScheme
                                    Case enumLayerType.Polygon : Scheme = New PolygonScheme
                                End Select
                            End With
                        Case enumLayerType.Raster
                            With CType(Layer, RasterLayer)
                                FileName = .DataSet.Filename
                                Symbolizer = .Symbolizer
                            End With
                        Case enumLayerType.Image
                            With CType(Layer, ImageLayer)
                                FileName = .DataSet.Filename
                                Symbolizer = .Symbolizer
                            End With
                    End Select

                    .LegendText = IniFile.GetKeyText(Section, enumKey.LegendText.ToString, IO.Path.GetFileNameWithoutExtension(FileName))
                    .IsVisible = IniFile.GetKeyText(Section, enumKey.Visible.ToString, True)

                    If TypeOf Layer Is FeatureLayer Then

                        Layer.Reproject(ProjectProjection)

                        With Scheme
                            .ClearCategories()

                            .LegendText = IniFile.GetKeyText(Section, enumKey.ThemeLegendText.ToString, "")
                            .AppearsInLegend = IniFile.GetKeyText(Section, enumKey.AppearsInLegend.ToString, True)
                            Dim NumCat As Integer = IniFile.GetKeyText(Section, enumKey.NumCategories.ToString, "0")
                            Dim cat As FeatureCategory = Nothing
                            Dim IsLegacy As Boolean = False

                            If NumCat = 0 Then 'is legacy file
                                IsLegacy = True
                                NumCat = 1
                            End If

                            For i As Integer = 1 To NumCat
                                Dim SectionCat As String = Section & "_Cat_" & i
                                If IsLegacy Then SectionCat = Section

                                Select Case LayerType(Layer)
                                    Case enumLayerType.Line
                                        Dim ss As New SimpleStroke(IniFile.GetKeyText(SectionCat, enumKey.LineWidth.ToString, 1),
                                                                   Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.LineColor.ToString, 0))))
                                        ss.DashStyle = IniFile.GetKeyText(SectionCat, enumKey.LineStipple.ToString, Drawing2D.DashStyle.Solid)
                                        Dim ls As New LineSymbolizer()
                                        ls.Strokes.Clear()
                                        ls.Strokes.Add(ss)
                                        cat = New LineCategory(ls)
                                        cat.FilterExpression = IniFile.GetKeyText(SectionCat, enumKey.FilterExpression.ToString, "")
                                        cat.LegendText = IniFile.GetKeyText(SectionCat, enumKey.LegendText.ToString, "")
                                        cat.LegendItemVisible = IniFile.GetKeyText(SectionCat, enumKey.LegendItemVisible.ToString, True)

                                    Case enumLayerType.Polygon
                                        Dim ss As New SimpleStroke
                                        ss.Color = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(Section, enumKey.LineColor.ToString, 0)))
                                        ss.Width = IniFile.GetKeyText(Section, enumKey.LineWidth.ToString, "0")
                                        ss.DashStyle = Val(IniFile.GetKeyText(Section, enumKey.LineStipple.ToString, Drawing2D.DashStyle.Solid))
                                        Dim ls As New LineSymbolizer
                                        ls.Strokes.Add(ss)

                                        Dim FillPattern As String = IniFile.GetKeyText(SectionCat, enumKey.PatternType.ToString, "Simple")
                                        Dim pat As IPattern = Nothing

                                        Select Case FillPattern
                                            Case "Gradient"
                                                pat = New GradientPattern()
                                                With CType(pat, GradientPattern)
                                                    .GradientType = IniFile.GetKeyText(SectionCat, enumKey.GradientType.ToString, GradientType.Circular)
                                                    .Angle = IniFile.GetKeyText(SectionCat, enumKey.GradientAngle.ToString, 0)
                                                    Dim numColors As Integer = IniFile.GetKeyText(SectionCat, enumKey.NumGradientColors.ToString, 0)
                                                    ReDim .Colors(numColors - 1), .Positions(numColors - 1)
                                                    For j As Integer = 0 To numColors - 1
                                                        Dim c As Color = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.FillColor.ToString & "_" & j + 1, 0)))
                                                        Dim p As Single = IniFile.GetKeyText(SectionCat, enumKey.Position.ToString & "_" & j + 1, 0)
                                                        .Colors(j) = c
                                                        .Positions(j) = p
                                                    Next
                                                    .UseOutline = IniFile.GetKeyText(SectionCat, enumKey.UseOutline.ToString, True)
                                                    .Outline = ls
                                                End With
                                            Case "Simple"
                                                pat = New SimplePattern(Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.FillColor.ToString, 0))))
                                                With CType(pat, SimplePattern)
                                                    .Opacity = IniFile.GetKeyText(SectionCat, enumKey.Opacity.ToString, 1.0)
                                                    .UseOutline = IniFile.GetKeyText(SectionCat, enumKey.UseOutline.ToString, True)
                                                    .Outline = ls
                                                End With
                                            Case "Hatch"
                                                pat = New HatchPattern()
                                                With CType(pat, HatchPattern)
                                                    .HatchStyle = IniFile.GetKeyText(SectionCat, enumKey.HatchStyle.ToString, Drawing2D.HatchStyle.DiagonalCross)
                                                    .ForeColor = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.ForeColor.ToString, 0)))
                                                    .BackColor = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.BackColor.ToString, 0)))
                                                    .ForeColorOpacity = IniFile.GetKeyText(SectionCat, enumKey.Opacity.ToString, 1.0)
                                                    .UseOutline = IniFile.GetKeyText(SectionCat, enumKey.UseOutline.ToString, True)
                                                    .Outline = ls
                                                End With
                                        End Select

                                        Dim ps As New PolygonSymbolizer
                                        If pat IsNot Nothing Then
                                            ps.Patterns.Clear()
                                            ps.Patterns.Add(pat)
                                        End If
                                        cat = New PolygonCategory(ps)
                                        cat.FilterExpression = IniFile.GetKeyText(SectionCat, enumKey.FilterExpression.ToString, "")
                                        cat.LegendText = IniFile.GetKeyText(SectionCat, enumKey.LegendText.ToString, "Unknown")
                                        cat.LegendItemVisible = IniFile.GetKeyText(SectionCat, enumKey.LegendItemVisible.ToString, True)

                                    Case enumLayerType.Point
                                        Dim PointShape As DotSpatial.Symbology.PointShape = IniFile.GetKeyText(SectionCat, enumKey.PointShape.ToString, DotSpatial.Symbology.PointShape.Ellipse)
                                        Dim PointSize As Double = Val(IniFile.GetKeyText(SectionCat, enumKey.PointSize.ToString, "12"))
                                        Dim PointColor As Drawing.Color = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.PointColor.ToString, 0)))
                                        Dim UseOutline As Boolean = IniFile.GetKeyText(SectionCat, enumKey.UseOutline.ToString, "True")
                                        Dim OutlineColor As Drawing.Color = Drawing.ColorTranslator.FromWin32(Val(IniFile.GetKeyText(SectionCat, enumKey.OutlineColor.ToString, 0)))
                                        Dim PointAngle As Double = Val(IniFile.GetKeyText(SectionCat, enumKey.PointAngle.ToString, "90"))

                                        Dim ss As New SimpleSymbol(PointColor, PointShape, PointSize)
                                        ss.UseOutline = UseOutline
                                        ss.OutlineColor = OutlineColor
                                        ss.Angle = PointAngle
                                        Dim ps As New PointSymbolizer(ss)

                                        cat = New PointCategory(ps)
                                        cat.FilterExpression = IniFile.GetKeyText(SectionCat, enumKey.FilterExpression.ToString, "")
                                        cat.LegendText = IniFile.GetKeyText(SectionCat, enumKey.LegendText.ToString, "Unknown")
                                        cat.LegendItemVisible = IniFile.GetKeyText(SectionCat, enumKey.LegendItemVisible.ToString, True)


                                End Select

                                cat.SelectionSymbolizer = SelectionSymbolizer
                                .AddCategory(cat)

                            Next

                        End With

                        With CType(Layer, FeatureLayer)
                            .Symbology = Scheme
                        End With

                    ElseIf LayerType(Layer) = enumLayerType.Raster Then

                        Dim rs As New RasterSymbolizer(Layer)
                        Dim cs As New ColorScheme
                        .LegendText = IniFile.GetKeyText(Section, enumKey.LegendText.ToString, "")
                        .LegendItemVisible = IniFile.GetKeyText(Section, enumKey.AppearsInLegend.ToString, True)
                        Dim NumCat As Integer = IniFile.GetKeyText(Section, enumKey.NumCategories.ToString, "0")

                        For i As Integer = 1 To NumCat
                            Dim SectionCat As String = Section & "_Cat_" & i
                            Dim cat As New ColorCategory(IniFile.GetKeyText(SectionCat, enumKey.MinValue.ToString, 0),
                                                          IniFile.GetKeyText(SectionCat, enumKey.MaxValue.ToString, 100),
                                                          Drawing.ColorTranslator.FromWin32(IniFile.GetKeyText(SectionCat, enumKey.LowColor.ToString, 0)),
                                                          Drawing.ColorTranslator.FromWin32(IniFile.GetKeyText(SectionCat, enumKey.HighColor.ToString, 0)))
                            cat.LegendText = IniFile.GetKeyText(SectionCat, enumKey.LegendText.ToString, "")
                            cs.AddCategory(cat)
                        Next i
                        rs.Scheme = cs
                        rs.NoDataColor = Drawing.ColorTranslator.FromWin32(IniFile.GetKeyText(Section, enumKey.NoDataColor.ToString, 0))
                        With CType(Layer, RasterLayer)
                            .Symbolizer = rs
                            .WriteBitmap()
                        End With

                    ElseIf LayerType(Layer) = enumLayerType.Image Then

                        'no properties to set!

                    End If

                End With

            End If



        Catch ex As Exception

            ErrorMsg(, ex)

        Finally

            GISMap.Layers.ResumeEvents()
            ProgressComplete()

        End Try

    End Sub

    ''' <summary>
    ''' List of selected station IDs
    ''' </summary>
    Public Function SelectedStations() As Generic.List(Of String)
        Return lstStations
    End Function

    ''' <summary>
    ''' Return rectangle for area of interest (nothing if not specified)
    ''' </summary>
    Public Function AOIRectangle() As RectangleF
        Return _AOIRectangle
    End Function

    Friend Sub SetPointStyle(ByRef Layer As MapPointLayer, PointShape As DotSpatial.Symbology.PointShape, PointSize As Integer, FillColor As Color, OutlineColor As Color, Angle As Double)
        'in case is thematic mapping, set size of max category (will refresh display soon)
        If Layer.Symbology.NumCategories > 0 Then
            CType(Layer.Symbology.GetCategories(Layer.Symbology.NumCategories - 1).Symbolizer, DotSpatial.Symbology.PointSymbolizer).Symbols(0).Size = New DotSpatial.Symbology.Size2D(PointSize, PointSize)
        End If
        Dim sym As New DotSpatial.Symbology.PointSymbolizer
        Dim ss As New DotSpatial.Symbology.SimpleSymbol(FillColor, PointShape, PointSize)
        ss.OutlineColor = OutlineColor
        ss.OutlineWidth = 1
        ss.UseOutline = True
        ss.Angle = Angle
        sym.Symbols.Clear()
        sym.Symbols.Add(ss)
        Layer.Symbolizer = sym
    End Sub

    ''' <summary>
    ''' Given a filename, use default provider to load the dataset and create an appropriate map layer
    ''' </summary>
    ''' <param name="FileName">Name of file to open</param>
    Private Function OpenLayer(FileName As String) As IMapLayer
        'layer is loaded into memory but not yet added to map

        'DotSpatial.Data.DataManager.DefaultDataManager.ProgressHandler = New clsMapProgressHandler
        Dim ds As DataSet = DotSpatial.Data.DataManager.DefaultDataManager.OpenFile(FileName)
        Dim lyr As IMapLayer = Nothing

        If TypeOf ds Is LineShapefile Then
            lyr = New MapLineLayer(ds)
        ElseIf TypeOf ds Is PolygonShapefile Then
            lyr = New MapPolygonLayer(ds)
        ElseIf TypeOf ds Is PointShapefile Then
            lyr = New MapPointLayer(ds)
        ElseIf TypeOf ds Is Raster Then
            lyr = New MapRasterLayer(ds)
        ElseIf TypeOf ds Is InRamImageData Then
            lyr = New MapImageLayer(ds)
        Else
            Throw New ApplicationException("Unsupported dataset type found: " & ds.GetType.ToString)
        End If
        Return lyr
    End Function

    ''' <summary>
    ''' Shared function to create new map layer using specified shapefile
    ''' </summary>
    Private Function AddLayer(FileName As String, Optional MapGroup As IMapGroup = Nothing, Optional SuppressWarnings As Boolean = False) As Boolean

        If MapGroup Is Nothing Then MapGroup = GISMap.MapFrame
        Dim lyr As IMapLayer = Nothing

        Try
            If Not My.Computer.FileSystem.FileExists(FileName) Then Return False
            With GISMap
                '.Layers.SuspendEvents()
                WriteProgress("Loading file: {0}", IO.Path.GetFileName(FileName))
                Application.DoEvents()

                lyr = OpenLayer(FileName)

                ProgressComplete()

                If lyr IsNot Nothing Then

                    Dim IsNewProjectProjection As Boolean = False

                    'later ones must compare; if WRDB Stations layer already has been added, reproject it without comment

                    Dim IsStationsLayer As Boolean = IO.Path.GetFileNameWithoutExtension(FileName).Equals(WRDBFileName, StringComparison.OrdinalIgnoreCase)

                    If lyr.Projection Is Nothing OrElse Not lyr.Projection.IsValid Then
                        Dim projString As String = ""
                        If lyr.Projection IsNot Nothing Then projString = " (" & lyr.Projection.ToString & ")"
                        Select Case frmSelectOption.SelectOption(Me, "No Projection Found or Projection Invalid",
                                                                    String.Format("This file ({0}) has no assigned projection or was found to be invalid{1}. What do you want to do?", IO.Path.GetFileName(FileName), projString),
                                                                    "&Automatically do this for the remainder of the session",
                                                                    "&Change the layer projection so that it matches the map",
                                                                    "Assume the coordinates are Latitude/Longitude and assign &WGS84 projection",
                                                                    "Assume the coordinates are &USA Contiguous Albers Equal Area USGS projection")
                            Case frmSelectOption.enumResult.Button1 : lyr.Projection = GISMap.Projection
                            Case frmSelectOption.enumResult.Button2 : lyr.Projection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
                            Case frmSelectOption.enumResult.Button3 : lyr.Projection = DotSpatial.Projections.KnownCoordinateSystems.Projected.NorthAmerica.USAContiguousAlbersEqualAreaConicUSGS
                            Case frmSelectOption.enumResult.Cancel : Return False
                        End Select
                        If TypeOf lyr Is FeatureLayer Then
                            CType(lyr, FeatureLayer).DataSet.Save()
                        ElseIf TypeOf lyr Is RasterLayer Then
                            CType(lyr, RasterLayer).DataSet.Save()
                        ElseIf TypeOf lyr Is ImageLayer Then
                            CType(lyr, ImageLayer).DataSet.Save()
                        End If
                    End If

                    If ProjectProjection IsNot Nothing AndAlso (lyr.Projection IsNot Nothing AndAlso Not lyr.Projection.Equals(ProjectProjection)) AndAlso Not SuppressWarnings Then
                        If Not IsStationsLayer Then

                            Dim ReprojectLayer As Boolean = False
                            Dim LyrProjection As String = "Nothing"
                            If lyr.Projection IsNot Nothing Then LyrProjection = lyr.Projection.ToString

                            Select Case frmSelectOption.SelectOption(Me, "Map Projection Mismatch",
                                                                        String.Format("This file ({2}) is using a different projection ({0}) than this map ({1}). What do you want to do?",
                                                                                    LyrProjection, ProjectProjection.ToString, IO.Path.GetFileName(FileName)),
                                                                        "&Automatically do this for the remainder of the session",
                                                                        String.Format("&Reproject the file {0} so it can be viewed properly (the original file will not be altered)", IIf(TypeOf lyr Is FeatureLayer, "in memory", "on disk")),
                                                                        "&Ignore the projection mismatch and load the layer as-is (the items on the map may be misaligned)",
                                                                        IIf(.Layers.Count = 0, "&Change the map projection so that it matches the layer (and will be used from now on)", Nothing))
                                Case frmSelectOption.enumResult.Button1 : ReprojectLayer = True
                                Case frmSelectOption.enumResult.Button2 'don't do anything 
                                Case frmSelectOption.enumResult.Button3 : ProjectProjection = lyr.Projection : IsNewProjectProjection = True
                                Case frmSelectOption.enumResult.Cancel : .Layers.Remove(lyr) : Return False
                            End Select

                            If ReprojectLayer Then

                                If TypeOf lyr Is FeatureLayer Then
                                    lyr.Reproject(ProjectProjection)

                                ElseIf TypeOf lyr Is RasterLayer Then
                                    lyr.Reproject(ProjectProjection)
                                    'Dim NewFileName As String = Nothing

                                    'WriteProgress("Reprojecting grid file: " & IO.Path.GetFileName(FileName))

                                    'Dim mwProgress As New MapWinProgress 'this will capture resampling progress
                                    'MapWinUtility.Logger.ProgressStatus = mwProgress

                                    'If D4EM.Geo.SpatialOperations.ProjectGrid(FileName, ProjectProjection, NewFileName) Then
                                    '    lyr = New MapRasterLayer(DotSpatial.Data.DataManager.DefaultDataManager.OpenFile(NewFileName))
                                    'Else
                                    '    ProgressComplete()
                                    '    WarningMsg("Unable to reproject the grid file. The destination file may be already open in this or another application or the assigned projection may be invalid.")
                                    '    Return False
                                    'End If

                                ElseIf TypeOf lyr Is MapImageLayer Then
                                    lyr.Reproject(ProjectProjection)

                                Else
                                    WarningMsg("Invalid layer type encountered.")
                                    Return False
                                End If

                            End If
                        Else 'automatically reproject the stations layer without asking
                            lyr.Reproject(ProjectProjection)
                            'WarningMsg("Layer type is not supported.")
                            'Return False
                        End If

                    Else
                        lyr.Projection = ProjectProjection
                    End If

                    MapGroup.Layers.Add(lyr)

                    For Each lyr In GISMap.Layers
                        lyr.SelectionEnabled = False
                    Next

                    lyr.SelectionEnabled = True

                    'For Each l As IMapLayer In GISMap.Layers
                    '    l.IsSelected = False
                    'Next
                    'lyr.IsSelected = True
                    'GISMap.Layers.SelectedLayer = lyr
                    'GISLegend.RefreshNodes()
                    'GISLegend.ResetScroll()
                    'GISLegend.Refresh()
                    SelectLayer(lyr)
                    GISLegend.RefreshNodes()
                    GISLegend.ResetScroll()
                    GISLegend.Refresh()

                    'find existing WRDB Stations layer and make sure it uses this projection (will not apply if adding WRDB Stations at this time, because is not a layer yet)
                    For i As Integer = 0 To .Layers.Count - 1
                        If String.Equals(.Layers(i).LegendText, WRDBStationsLayerName, StringComparison.OrdinalIgnoreCase) AndAlso Not .Layers(i).Projection.Equals(ProjectProjection) Then
                            .Layers(i).Reproject(ProjectProjection)
                        End If
                    Next

                    'following was previously used with D4EM but that has been deprecated 9/1/16
                    'ApplyRendering(lyr, FileName, "")

                    'standard rendering may have the visibility set to false, which is fine when your are downloading a lot of layers, but if you manually add one, always make it visible
                    lyr.IsVisible = True
                    GISLegend.Refresh()

                    If IsNewProjectProjection Then .ZoomToMaxExtent()

                End If

                If .Layers.Count = 1 Then 'had no layer open before, so zoom extents
                    ZoomExtents()
                End If

            End With

            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return Nothing
        Finally
            GISMap.Layers.ResumeEvents()
            ProgressComplete()
        End Try
    End Function

    ''' <summary>
    ''' Reset selected layer
    ''' </summary>
    ''' <param name="lyr">Layer to select</param>
    ''' <remarks>This approach is very poorly implemented; unknown why the legend doesn't automatically update and why you must set both IsSelected and SelectedLayer</remarks>
    Private Sub SelectLayer(lyr As IMapLayer)
        GISLegend.ClearSelection()
        lyr.IsSelected = True
        'GISMap.Layers.SelectedLayer = lyr
    End Sub

    ''' <summary>
    ''' Try to get desired rendering information from XML file or .mwsr files
    ''' </summary>
    ''' <param name="lyrFile">Name of file containing layer</param>
    ''' <param name="GroupName">Desired group name</param>
    ''' <remarks></remarks>
    Friend Sub ApplyRendering(lyr As IMapLayer, lyrFile As String, GroupName As String)

        WriteProgress("Applying color scheme...")

        Dim ds As New Data.DataSet

        'this file contains information about desired layer and group names
        'can alternatively get this information from .mwsr files (and also rendering information)

        ds.ReadXml(My.Application.Info.DirectoryPath & "\renderers\BasinsDefaultLayers.xml")
        Dim dtLayers As DataTable = ds.Tables("Layer")

        'GetBasins contains XML with layers acquired, but will just load layers by looking for shape files

        'use XML file from BASINS to assign layer name, group, and initial visibility

        Dim filename As String = IO.Path.GetFileName(lyrFile)
        If filename.Contains("_") AndAlso filename.Length >= 8 AndAlso IsNumeric(filename.Substring(0, 8)) Then 'strip off 8 digit huc before underscore
            filename = filename.Substring(8)
        ElseIf IO.Path.GetFileNameWithoutExtension(lyrFile).ToLower = "rf1" Then
            filename = "rf1" 'this is the only one without an extension
        End If

        Dim LayerName As String = Proper(IO.Path.GetFileNameWithoutExtension(lyrFile))
        Dim Visible As Boolean = True
        Dim Expanded As Boolean = True
        Dim LabelsVisible As Boolean = True

        'first, try to get from mwsr file located in renderers folder

        Dim mwsrFile As String = String.Format("renderers\{0}.mwsr", IO.Path.GetFileNameWithoutExtension(filename))

        Dim dsMwsr As Data.DataSet = Nothing
        If My.Computer.FileSystem.FileExists(mwsrFile) Then
            dsMwsr = New Data.DataSet
            dsMwsr.ReadXml(mwsrFile)
            Dim dr = dsMwsr.Tables("Layer").Rows(0)
            If dsMwsr.Tables("Layer").Columns.Contains("Layer") Then GroupName = dr("GroupName")
            LayerName = dr("Name")
            Visible = dr("Visible")
            Expanded = dr("Expanded")
            LabelsVisible = dr("LabelsVisible")
        Else
            'if cannot find it, get from BasinsDefaultLayers.xml

            Dim dr() As DataRow = Nothing

            Try
                dr = dtLayers.Select("[Filename] LIKE '*" & filename & "'")
            Catch ex As Exception

            End Try

            If dr IsNot Nothing AndAlso dr.Length = 1 Then
                GroupName = dr(0)("Group")
                LayerName = dr(0)("Name")
                Visible = TestNull(dr(0)("Visible"), "") = "yes"
            Else
                Debug.Print("No Match in XML: " & filename)
                'Debug.Assert(False)
            End If
        End If

        Dim group As MapGroup = Nothing
        For Each grp In GISForm.GISMap.GetAllGroups
            If grp.LegendText = GroupName Then
                group = grp
                Exit For
            End If
        Next

        If group Is Nothing AndAlso Not String.IsNullOrEmpty(GroupName) Then
            group = New MapGroup(GISForm.GISMap, GroupName)
        End If

        If group IsNot Nothing Then
            group.Layers.Add(lyr)
            GISMap.Layers.Remove(lyr)
        End If

        lyr.LegendText = LayerName
        lyr.IsVisible = Visible
        lyr.IsExpanded = Expanded

        If dsMwsr IsNot Nothing Then
            Dim dr = dsMwsr.Tables("ShapeFileProperties").Rows(0)
            If TypeOf lyr Is MapPolygonLayer Then
                With CType(lyr, MapPolygonLayer)
                    Dim sp As New SimplePattern(Drawing.ColorTranslator.FromOle(dr("Color")))
                    sp.Outline = New LineSymbolizer(Drawing.ColorTranslator.FromOle(dr("OutlineColor")), dr("LineOrPointSize"))
                    sp.Opacity = dr("TransparencyPercent")
                    If Not CBool(dr("Drawfill")) Then sp.FillColor = Color.Transparent
                    .Symbolizer = New PolygonSymbolizer
                    .Symbolizer.Patterns.Clear()
                    .Symbolizer.Patterns.Add(sp)
                End With
            ElseIf TypeOf lyr Is MapLineLayer Then
                With CType(lyr, MapLineLayer)
                    Dim sym As New LineSymbolizer(Drawing.ColorTranslator.FromOle(dr("OutlineColor")), dr("LineOrPointSize"))
                    .Symbolizer = sym
                End With
            End If
        End If

        'if mwleg file is found in same folder, create scheme
        Dim mwlegFile As String = IO.Path.ChangeExtension(lyrFile, ".mwleg")
        If My.Computer.FileSystem.FileExists(mwlegFile) Then
            Dim dsMwleg As New Data.DataSet
            dsMwleg.ReadXml(mwlegFile)
            If TypeOf lyr Is RasterLayer Then
                Dim rs As New RasterSymbolizer(lyr)
                Dim scheme As New ColorScheme()
                For Each dr As DataRow In dsMwleg.Tables("Break").Rows
                    Dim cat As New ColorCategory(CDbl(dr("LowValue")),
                                                 CDbl(dr("HighValue")),
                                                 Drawing.ColorTranslator.FromHtml(dr("LowColor")),
                                                 Drawing.ColorTranslator.FromHtml(dr("HighColor")))
                    cat.LegendText = dr("Caption")
                    scheme.AddCategory(cat)
                Next
                rs.Scheme = scheme
                rs.NoDataColor = Drawing.ColorTranslator.FromHtml(dsMwleg.Tables("GridColoringScheme").Rows(0)("NoDataColor"))
                With CType(lyr, RasterLayer)
                    .Symbolizer = rs
                    .WriteBitmap()
                End With
            End If
            dsMwleg.Dispose()
        End If

        'If Not lyr.Projection.Equals(ProjectProjection) Then lyr.Reproject(ProjectProjection)

        GISForm.MoveBelowPolylineLayers(lyr)
        GISForm.WriteProgress("Loading {0}...", IO.Path.GetFileNameWithoutExtension(lyrFile))

        If group IsNot Nothing AndAlso Not GISForm.GISMap.GetAllGroups.Contains(group) Then GISForm.GISMap.Layers.Add(group)

        'GISLegend.RefreshNodes()

        GISForm.GISLegend.ResetScroll()

        ProgressComplete()

    End Sub

    Private Sub AnimTimer_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles AnimTimer.Tick
        AnimTimer.Enabled = False
        IsCancelled = False
        'if selected Turbo speed up by skipping increments
        If AnimationInfo.Speed = 6 Then
            IncrementTrackbar(2)
        ElseIf AnimationInfo.Speed = 7 Then
            IncrementTrackbar(4)
        Else
            IncrementTrackbar(1)
        End If
        If Not IsCancelled Then AnimTimer.Enabled = True
    End Sub

    Private Sub IncrementTrackbar(NumSteps As Integer)
        With trkAnim
            If .Value = .Maximum Then
                .Value = .Minimum
            Else
                .Value = Math.Min(.Maximum, .Value + .SmallChange * NumSteps)
            End If
        End With
    End Sub

    Private Sub btnHelp_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHelp.Click
        mnuHelpContents.PerformClick()
    End Sub

    Private Sub btnLocation_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnLocation.Click
        LocatePlace()
    End Sub

    Private Sub btnWhatsThis_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnWhatsThis.Click
        mnuHelpWhatsThis.PerformClick()
    End Sub

    Private Sub ResetAggregate()
        Select Case cboAggregation.Text
            Case "Period of Record" : Aggregate = enumAggregate.POR
            Case "None" : Aggregate = enumAggregate.None
            Case "Day" : Aggregate = enumAggregate.Day
            Case "Month" : Aggregate = enumAggregate.Month
            Case "Growing Season" : Aggregate = enumAggregate.Growing
            Case "Annual" : Aggregate = enumAggregate.Annual
            Case Else
                Throw New Exception("Unexpected value for aggregation")
        End Select
    End Sub

    Private Sub cbo_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboAggregation.SelectedIndexChanged, cboStatistic.SelectedIndexChanged, cboVariable.SelectedIndexChanged, cboLayer.SelectedIndexChanged, cboStationType.SelectedIndexChanged
        Try

            If AnimationInfo.IsAnimating Then
                AnimationInfo.IsAnimating = False
                AnimTimer.Stop()
            End If

            If SuspendMapUpdate Then Exit Sub
            IsCancelled = False
            'set focus to map
            GISMap.Focus()
            Me.Enabled = False 'so don't change again before summarize is done
            If sender Is cboStatistic Then
                CheckDropDownItem(mnuDataStatistic, cboStatistic.SelectedIndex)
                SummarizeStationData()
            ElseIf sender Is cboAggregation Then
                mnuViewAnimate.Enabled = cboAggregation.SelectedIndex <> 0
                mnuViewAnimateSpeed.Enabled = cboAggregation.SelectedIndex <> 0
                ResetAggregate()
                Select Case Aggregate
                    Case enumAggregate.None
                        cboStatistic.SelectedIndex = -1
                        cboStatistic.Enabled = False
                    Case Else
                        cboStatistic.SelectedIndex = 3 'mean
                        cboStatistic.Enabled = True
                End Select
                CheckDropDownItem(mnuDataAggregation, Aggregate)
                If Mode = enumMode.OutputData Then
                    SummarizeStationData()
                End If
            ElseIf sender Is cboLayer Then
                With AnimationInfo
                    .StationList.Clear()
                    Select Case .DataLinkageType
                        Case enumLinkageType.StationID, enumLinkageType.SegmentNum 'either Station_ID or SEGID
                            Dim fld As Integer = GetFieldIndex(.DataLayer.DataSet.DataTable, .DataAttribute)
                            If fld <> -1 Then
                                For i As Integer = 0 To .DataLayer.DataSet.Features.Count - 1
                                    Dim StaID As String = TestNull(.DataLayer.DataSet.DataTable.Rows(i)(fld), "")
                                    If Not .StationList.Contains(StaID) Then .StationList.Add(StaID)
                                Next
                            End If
                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK 'get I, J and K from separate hardwired attributes I, J, and K
                            For i As Integer = 0 To .DataLayer.DataSet.Features.Count - 1
                                Dim IJK As String = String.Format("{0},{1}", TestNull(.DataLayer.DataSet.DataTable.Rows(i)("I"), ""), TestNull(.DataLayer.DataSet.DataTable.Rows(i)("J"), ""))
                                If cboLayer.Text <> "" Then IJK &= "," & Val(cboLayer.Text)
                                If Not .StationList.Contains(IJK) Then .StationList.Add(IJK)
                            Next
                    End Select

                    .StationList.Sort()
                End With

                SummarizeStationData()
                CheckDropDownItem(mnuDataLayer, cboLayer.SelectedIndex)
            ElseIf sender Is cboVariable Then
                SummarizeStationData()
                CheckDropDownItem(mnuDataVariable, cboVariable.SelectedIndex)
            ElseIf sender Is cboStationType Then
                SummarizeStationData()
                RefreshStationsDisplay()
            End If
            ResetAnimation()
            'SummarizeStationData()
            'RefreshStationsDisplay()
            trkAnim.Value = Math.Min(trkAnim.Maximum, 0)
            ResetTrackbar()
            'ProgressComplete()
            tblAnim.Enabled = cboAggregation.Text <> "Period of Record" And cboVariable.Text <> "None"
            picLegend.Visible = AnimationInfo.dsData IsNot Nothing AndAlso cboVariable.Text <> "None"
            tbLegend.Checked = picLegend.Visible
            mnuViewLegend.Checked = picLegend.Visible
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Me.Enabled = True
        End Try
    End Sub

    Friend Sub CopyMap(ByVal Map As DotSpatial.Controls.Map)
        Try
            Dim MyImage As New System.Drawing.Bitmap(Map.ClientRectangle.Width, Map.ClientRectangle.Height)
            Map.DrawToBitmap(MyImage, New Rectangle(0, 0, Map.ClientRectangle.Width, Map.ClientRectangle.Height))
            My.Computer.Clipboard.SetImage(MyImage)

            'Bitblasts a snapshot of the MapWindow map to the clipboard in VB Net
            'Dim g1 As System.Drawing.Graphics = AxMap1.CreateGraphics()
            'Dim MyImage As New System.Drawing.Bitmap(AxMap1.ClientRectangle.Width, AxMap1.ClientRectangle.Height, g1)
            'Dim g2 As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(MyImage)
            'Dim dc1 As IntPtr = g1.GetHdc()
            'Dim dc2 As IntPtr = g2.GetHdc()
            'Dim datobj As New System.Windows.Forms.DataObject
            'BitBlt(dc2, 0, 0, AxMap1.ClientRectangle.Width, AxMap1.ClientRectangle.Height, dc1, 0, 0, 13369376)
            'g1.ReleaseHdc(dc1)
            'g2.ReleaseHdc(dc2)
            'datobj.SetData(System.Windows.Forms.DataFormats.Bitmap, MyImage)
            'System.Windows.Forms.Clipboard.SetDataObject(datobj)
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Function GetLayer(FeatureSet As FeatureSet) As FeatureLayer
        For Each lyr As FeatureLayer In GISMap.GetAllLayers
            If lyr.DataSet.Equals(FeatureSet) Then Return lyr
        Next
        Return Nothing
    End Function

    ''' <summary>
    ''' Called when user has executed a query in WRDB and it has completed: update thematic map with results by recreating a shp file
    ''' </summary>
    Private Sub CreateStationsLayer(Optional ByVal StationsList As Generic.List(Of String) = Nothing)
        Try

            'if stations layer already exists, remove it
            For Each lyr As DotSpatial.Controls.IMapLayer In GISMap.Layers
                If lyr.LegendText = WRDBStationsLayerName Then
                    GISMap.Layers.Remove(lyr)
                    Exit For
                End If
            Next

            WriteProgress("Checking Stations file...")

            If Mode = enumMode.DisplayStations AndAlso WRDBProject.IsStationShapefileValid(WRDBStationsFileName) Then
                'stations shapefile already exists; just use it
                WriteProgress("Loading Stations file...")
                WRDBStationsLayer = GISMap.Layers.Add(WRDBStationsFileName)
                If WRDBStationsLayer IsNot Nothing Then
                    With WRDBStationsLayer
                        'let layer attributes be set by user
                        .LegendText = WRDBStationsLayerName
                        'default stations display to circles for thematic mapping based on size; all others to triangle
                        WriteProgress("Setting point style...")
                        Select Case Mode
                            Case enumMode.Animate, enumMode.OutputData
                                SetPointStyle(WRDBStationsLayer, DotSpatial.Symbology.PointShape.Ellipse, 8, Color.Red, Color.Black, 0)
                            Case Else
                                SetPointStyle(WRDBStationsLayer, DotSpatial.Symbology.PointShape.Triangle, 8, Color.Red, Color.Black, 90)
                        End Select
                    End With
                End If
                GISLegend.Text = WRDBStationsLayerName
                Exit Sub
            End If

            Try
                Dim dtStations As DataTable = Nothing
                If StationsList Is Nothing Then
                    Dim SQL As String = ""
                    Dim fieldList As String = "Station_ID as Station_ID,Station_Name as Stat_Name,Station_Type as Stat_Type,Latitude,Longitude"
                    If Not WRDBProject.GisQuickTable Then fieldList &= ",Data_Freq,Elevation,Branch_ID,Reach_ID,River_Mile,Dr_Area,Stations.Agency as Agency,Ref_ID,Remarks,Other,HUC"
                    With WRDBProject
                        If ModeSettings.SelectStationsFromDataTable Then
                            SQL = $"SELECT DISTINCT Dta.{fieldList} FROM ({ModeSettings.DataTableName} Dta LEFT OUTER JOIN {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} STATIONS ON Dta.Station_ID=Stations.Station_ID) WHERE Latitude IS NOT NULL AND Longitude IS NOT NULL"
                            If .DB.GetColumnList(ModeSettings.DataTableName).Contains("SELECTED_BY") Then
                                SQL &= String.Format(" AND Selected_By='{0}'", clsApplicationSettings.UserName)
                            End If
                        Else
                            SQL = $"SELECT {fieldList } FROM {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} STATIONS WHERE Latitude IS NOT NULL AND Longitude IS NOT NULL"
                        End If
                        WriteProgress("Loading data...")
                        If Not .DB.AddTable(dtStations, SQL) Then Exit Sub
                        ProgressComplete()
                    End With
                    dtStations.PrimaryKey = New DataColumn() {dtStations.Columns("Station_ID")}
                    If dtStations.Rows.Count = 0 Then Exit Sub
                Else
                    dtStations.Columns.Add("Station_ID", GetType(String))
                    dtStations.Columns.Add("Station_Name", GetType(String))
                    dtStations.Columns.Add("Station_Type", GetType(String))
                    dtStations.Columns.Add("Latitude", GetType(Double))
                    dtStations.Columns.Add("Longitude", GetType(Double))

                    WriteProgress("Loading data...")
                    For Each s As String In StationsList
                        dtStations.Rows.Add(s.Split(vbTab))
                        Application.DoEvents()
                    Next
                    ProgressComplete()
                    dtStations.PrimaryKey = New DataColumn() {dtStations.Columns("Station_ID")}
                    If dtStations.Rows.Count = 0 Then Exit Sub
                End If

                'add data so can do thematic mapping
                If dtResults IsNot Nothing Then
                    For i As Integer = 1 To dtResults.Columns.Count - 1
                        With dtResults.Columns(i)
                            'dtResult uses composite expressions so data types in table are usually Object; must explicitly map using name in SummarizeStationData
                            Dim dType As System.Type = GetType(Double)
                            If .ColumnName.ToUpper.Contains("NUM") Or .ColumnName.ToUpper.Contains("MONTH") Or .ColumnName.ToUpper.Contains("YEAR") Then
                                dType = GetType(Integer)
                            ElseIf .ColumnName.ToUpper.Contains("DATE") Then
                                dType = GetType(Date)
                            Else
                                dType = GetType(Double)
                            End If
                            dtStations.Columns.Add(.ColumnName, dType)
                        End With
                    Next

                    For Each row As DataRow In dtResults.Rows
                        Dim rowStations As DataRow = dtStations.Rows.Find(row.Item("Station_ID"))
                        If rowStations IsNot Nothing Then
                            For i As Integer = 1 To dtResults.Columns.Count - 1
                                Dim colName As String = dtResults.Columns(i).ColumnName
                                rowStations.Item(colName) = row.Item(colName)
                            Next
                        End If
                    Next
                End If

                'first remove all existing WRDB stations layers

                'If Not String.IsNullOrEmpty(WRDBStationsLayerName) Then
                With GISMap
                    For i As Integer = .Layers.Count - 1 To 0 Step -1
                        If .Layers(i).LegendText IsNot Nothing AndAlso .Layers(i).LegendText.Equals(WRDBStationsLayerName, StringComparison.OrdinalIgnoreCase) Then .Layers.RemoveAt(i) : Exit For
                    Next
                End With
                'End If

                'delete existing shp files and create and add new one

                Dim NumShapes As Integer = 0

                Dim WRDBStationsShapefile As New DotSpatial.Data.PointShapefile()

                With WRDBStationsShapefile
                    Dim fn As String = ""
                    Try
                        For i As Int16 = 1 To 5
                            Dim ext As String = Choose(i, "shp", "shx", "dbf", "prj", "mwsr")
                            fn = IO.Path.ChangeExtension(WRDBStationsFileName, ext)
                            If My.Computer.FileSystem.FileExists(fn) Then My.Computer.FileSystem.DeleteFile(fn)
                        Next
                    Catch ex As Exception
                        If My.Computer.FileSystem.FileExists(fn) Then
                            WarningMsg("Unable to delete file: {0}; it may be locked by another program.", fn)
                            Exit Sub
                        End If
                    End Try

                    If Not My.Computer.FileSystem.DirectoryExists(IO.Path.GetDirectoryName(WRDBStationsFileName)) Then My.Computer.FileSystem.CreateDirectory(IO.Path.GetDirectoryName(WRDBStationsFileName))

                    If WRDBStationsShapefile IsNot Nothing Then

                        'add one field for each field in datatable
                        .DataTable.Columns.Add("FID", GetType(Integer))
                        For i As Integer = 0 To dtStations.Columns.Count - 1
                            Dim c As DataColumn
                            With dtStations.Columns(i)
                                c = New DataColumn(.ColumnName, .DataType)
                            End With
                            .DataTable.Columns.Add(c)
                        Next

                        Dim rownum As Integer = 0

                        'if showing all station locations, create normal shape file with all stations; will add layer of normal points colored by station type
                        'if showing thematic map of values, shape file will contain only stations with data; will draw symbols of varying sizes

                        Dim minx As Double = Double.MaxValue, maxx As Double = -minx

                        .Features.SuspendEvents()

                        For Each r As DataRow In dtStations.Rows
                            Dim x As Single = -Math.Abs(TestNull(r.Item("Longitude"), 0))
                            Dim y As Single = TestNull(r.Item("Latitude"), 0)
                            If Math.Abs(x) > 1 And y > 1 And Math.Abs(x) < 180 And y < 90 Then
                                minx = Math.Min(minx, x)
                                maxx = Math.Max(maxx, x)
                                Dim pnt As New DotSpatial.Topology.Coordinate(x, y)
                                .Features.Add(pnt)
                                'above is much faster than this...
                                'Dim shp As New DotSpatial.Data.Shape(New DotSpatial.Topology.Coordinate(x, y))
                                '.AddShape(shp)
                                With .DataTable.Rows(.DataTable.Rows.Count - 1)
                                    .Item("FID") = WRDBStationsShapefile.DataTable.Rows.Count - 1
                                    For c As Integer = 0 To r.ItemArray.Count - 1
                                        .Item(c + 1) = r.Item(c)
                                    Next
                                End With
                            End If
                            rownum += 1
                            WriteProgress(rownum, dtStations.Rows.Count - 1, "Creating WRDB stations shape file (press ESC to cancel)...")
                            If IsCancelled Then Exit Sub 'note: if close form during this loop will crash
                        Next

                        WriteProgress("Saving shape file...")

                        NumShapes = .DataTable.Rows.Count

                        If NumShapes = 0 Then
                            WarningMsg("No stations have been added to the WRDB Stations layer because none of the selected data items referenced stations with valid latitude and longitude.")
                            .Close()
                            Exit Sub
                        Else
                            .Projection = New DotSpatial.Projections.ProjectionInfo
                            .Projection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
                            .InitializeVertices()
                            .UpdateExtent()
                            .InvalidateVertices()
                            .SaveAs(WRDBStationsFileName, True)
                            .Close()
                        End If

                        'need to reproject to the current setting for the MW project 

                        If NumShapes > 0 And ProjectProjection IsNot Nothing Then
                            Reproject(GISMap, WRDBStationsFileName, ProjectProjection, True)
                        End If
                    Else
                        'ErrorMsg(GISMap.get_ErrorMsg(GISMap.LastErrorCode))
                    End If
                End With

                'shape file is created, now add to view

                GISMap.Layers.SuspendEvents()

                WRDBStationsLayer = GISMap.AddLayer(WRDBStationsFileName)

                If WRDBStationsLayer IsNot Nothing AndAlso TypeOf WRDBStationsLayer Is MapPointLayer Then
                    With CType(WRDBStationsLayer, MapPointLayer)
                        'default stations display to circles for thematic mapping based on size; all others to triangle
                        Select Case Mode
                            Case enumMode.Animate, enumMode.OutputData
                                SetPointStyle(WRDBStationsLayer, DotSpatial.Symbology.PointShape.Ellipse, 20, Color.Red, Color.Black, 0)
                            Case Else
                                SetPointStyle(WRDBStationsLayer, DotSpatial.Symbology.PointShape.Triangle, 12, Color.Red, Color.Black, 90)
                        End Select

                        .LegendText = WRDBStationsLayerName
                    End With
                End If

                dtStations.Dispose()

            Catch ex As Exception
                ErrorMsg(, ex)
            Finally
                'If MainForm IsNot Nothing Then MainForm.ProgressComplete()
            End Try
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            If WRDBStationsLayer IsNot Nothing AndAlso Not IsCancelled AndAlso CType(WRDBStationsLayer, MapPointLayer).DataSet.NumRows < 1000 Then
                'add station id labels (note: there is a bug in DS; this works OK for stations display, but not for thematic mapping of values)
                WriteProgress("Adding labels...")

                WRDBStationsLayer.LabelLayer = New MapLabelLayer(WRDBStationsLayer)

                With WRDBStationsLayer.LabelLayer
                    With .Symbology.Categories
                        .Clear()
                        .Add(New LabelCategory With {.Expression = "[Station_ID]",
                                                     .Name = "Station ID",
                                                     .Symbolizer = New LabelSymbolizer With {.PreventCollisions = False,
                                                                                             .OffsetY = 8,
                                                                                             .Orientation = ContentAlignment.TopCenter,
                                                                                             .FontSize = 8,
                                                                                             .FontFamily = "Tahoma"}
                                                    })
                    End With
                    .CreateLabels()
                    .IsVisible = False
                End With
            End If
            GISMap.Layers.ResumeEvents()
            ProgressComplete()
        End Try
    End Sub

    ''' <summary>
    ''' Called when user has executed a query in WRDB and it has completed: update thematic map with results by recreating a shp file
    ''' </summary>
    Private Sub CreateGridLayer(Optional ByVal StationsList As Generic.List(Of String) = Nothing)
        Try

            If WRDBStationsLayer Is Nothing Then Exit Sub

            If WRDBGridLayer IsNot Nothing Then GISMap.Layers.Remove(WRDBGridLayer)

            Dim fieldname As String = ""
            Select Case Statistic()
                Case enumStatistic.Num : fieldname = "NumResult"
                Case enumStatistic.Min : fieldname = "MinResult"
                Case enumStatistic.Max : fieldname = "MaxResult"
                Case enumStatistic.Mean : fieldname = "AvgResult"
            End Select

            Dim lyrClip = GetLayer(GridClippingLayerFilename)
            If lyrClip Is Nothing Then lyrClip = WRDBStationsLayer

            WriteProgress("Creating grid...")
            WRDBGrid = New clsGrid(WRDBStationsLayer, fieldname, WRDBGridFileName, clsGrid.enumCellSizeOption.NumCells, GridSize, GridNumPoints, Double.MaxValue, 2, 100, lyrClip)
            With WRDBGrid
                WRDBGridLayer = .CreateGrid 'create and add to map
                If WRDBGridLayer Is Nothing Then Exit Sub
                WriteProgress("Computing spatial weights...")
                .ComputeWeights()
                WRDBGridLayer.LegendText = WRDBGridLayerName
                MoveBelowPolylineLayers(WRDBGridLayer)
                .lyrGrid = WRDBGridLayer
                ProgressComplete()
            End With

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub RefreshGrid()
        If WRDBGridLayer Is Nothing Then Exit Sub
        With WRDBGrid
            Dim fn As String = ""
            Select Case Statistic()
                Case enumStatistic.Num : fn = "NumResult"
                Case enumStatistic.Min : fn = "MinResult"
                Case enumStatistic.Max : fn = "MaxResult"
                Case enumStatistic.Mean : fn = "AvgResult"
            End Select
            If Not AnimationInfo.IsAnimating Then WriteProgress("Updating grid using inverse distance method...")
            .UpdateGrid(fn)
            WRDBGridLayer.DataSet.InvalidateVertices() 'this causes the layer to be redrawn
            If Not AnimationInfo.IsAnimating Then ProgressComplete()
        End With
    End Sub

    Private Sub CreateGridScheme()
        If WRDBGridLayer Is Nothing Then Exit Sub
        With WRDBGrid
            If Not AnimationInfo.IsAnimating Then WriteProgress("Creating grid color scheme...")
            If Statistic() = enumStatistic.Num Then
                .CreateScheme(AnimationInfo.MinValue, AnimationInfo.MaxValue)
            Else
                .CreateScheme(AnimationInfo.MinValue, AnimationInfo.MaxValue)
            End If
            If Not AnimationInfo.IsAnimating Then ProgressComplete()
        End With
    End Sub

    Private Sub frmGIS_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles Me.KeyPress
        If txtLocation.Focused And e.KeyChar = Chr(13) Then
            LocatePlace()
            e.Handled = True
        ElseIf e.KeyChar = Chr(27) Then
            CancelOperation()
        End If
    End Sub

    ''' <summary>
    ''' Called when ESC pressed or double-click when drawing polyline/polygon
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub CancelOperation()
        ClearHighlight()
        If MapMode = enumMapMode.PolygonCreate Or MapMode = enumMapMode.PolylineCreate Or MapMode = enumMapMode.PointCreate Then SaveShapes()
        If MapMode = enumMapMode.PolygonSelect Or MapMode = enumMapMode.PolylineSelect Then SelectByPoly()
        IsCancelled = True
        GISMap.FunctionMode = FunctionMode.Select
        MapMode = enumMapMode.Select
    End Sub

    Public Sub ClearHighlight()
        If ActiveFeatureLayer IsNot Nothing Then ActiveFeatureLayer.UnSelectAll()
    End Sub

    Private Property ActiveFeatureLayer As FeatureLayer
        Get
            If TypeOf GISMap.Layers.SelectedLayer Is FeatureLayer Then
                Return GISMap.Layers.SelectedLayer
            Else
                Return Nothing
            End If
        End Get
        Set(value As FeatureLayer)
            If GISMap.Layers.SelectedLayer IsNot value Then SelectLayer(value)
        End Set
    End Property

    Private Property ActiveRasterLayer As RasterLayer
        Get
            If TypeOf GISMap.Layers.SelectedLayer Is RasterLayer Then
                Return GISMap.Layers.SelectedLayer
            Else
                Return Nothing
            End If
        End Get
        Set(value As RasterLayer)
            If GISMap.Layers.SelectedLayer IsNot value Then SelectLayer(value)
        End Set
    End Property

#Region "GISMap Event Handling"

    Private startext As DotSpatial.Data.Extent
    Private startx, starty As Double
    Private isPanning As Boolean = False

    Private Sub GISMap_Invalidated(sender As Object, e As InvalidateEventArgs) Handles GISMap.Invalidated
        'when dragging layers in legend the wait cursor gets set somewhere; this seems like a bit of overkill, but it gets the job done
        Dim state As Object = Nothing
        Me.SynchContext.Send(
            Sub()
                Cursor = Cursors.Default
            End Sub, state)
    End Sub

    Private Sub GISMap_KeyUp(sender As Object, e As System.Windows.Forms.KeyEventArgs) Handles GISMap.KeyUp
        If e.KeyCode = Keys.Escape Then GISMap.FunctionMode = DotSpatial.Controls.FunctionMode.Select
    End Sub

    Private Sub GISMap_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles GISMap.MouseDown
        Try

            'Const WindowSize As Integer = 5

            Dim pnt As Coordinate = GISMap.PixelToProj(New Drawing.Point(e.X, e.Y))
            If e.Button = Windows.Forms.MouseButtons.Left Then

                Dim CntlPressed As Boolean = (Control.ModifierKeys And Keys.Control) > 0
                Dim ShiftPressed As Boolean = (Control.ModifierKeys And Keys.Shift) > 0

                Select Case MapMode

                    Case enumMapMode.Select
                        If ShiftPressed Then
                            MapMode = enumMapMode.XSect
                            lstPolygon.Clear()
                            lstPolygon.Add(pnt)
                        End If

                    Case enumMapMode.XSect
                        StatusLabel.Text = "Move mouse to end of cross section and release..."
                        lstPolygon.Clear()
                        lstPolygon.Add(pnt)

                    Case enumMapMode.PolygonCreate, enumMapMode.PolygonSelect, enumMapMode.PolylineCreate, enumMapMode.PolylineSelect
                        StatusLabel.Text = "Click additional points to form the polygon or polyline; press ESC, double-click, or right-click when finished..."
                        lstPolygon.Add(pnt)

                End Select
            ElseIf e.Button = Windows.Forms.MouseButtons.Middle Then
                'GISMap.FunctionMode = FunctionMode.Pan
            Else
                CancelOperation() 'if drawing polyline/polygon, finish
            End If


        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub GISMap_ExtentsChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles GISMap.ViewExtentsChanged
        'always reset after a pan or zoom to select mode
        'UPDATE: no! let it stay in selected mode so is similar to ArcGIS

        'GISMap.FunctionMode = DotSpatial.Controls.FunctionMode.Select

        lstCoords.Clear() 'force recomputation of coordinates in FindPoints
        'IsModified = True
    End Sub

    Private Sub GISMap_MouseDownEvent(ByVal sender As Object, ByVal e As MouseEventArgs) 'Handles GISMap.MouseDown
        If e.Button = Windows.Forms.MouseButtons.Right Then
            Me.ContextMenuMap.Show(Me, Me.PointToClient(Control.MousePosition))
        ElseIf Mode = enumMode.AreaOfInterest And AOIDrawingHandle <> -1 Then
            'aoi drawing
            AOIDrawingHandle = -1
        End If
    End Sub

    Private Sub GISMap_MouseMoveEvent(ByVal sender As Object, ByVal e As MouseEventArgs) Handles GISMap.MouseMove
        Static IsActive As Boolean = False
        If AnimationInfo.IsAnimating Then Exit Sub

        Try
            Static lasttxt As String = ""
            If IsActive Then Exit Sub 'so will finish with one lookup before starts another
            IsActive = True

            'When mouse hovers over a valid shape, show the name of the GISTag on the status bar

            If ProjectProjection Is Nothing Then IsActive = False : Exit Sub

            Dim pnt As Coordinate = GISMap.PixelToProj(New Drawing.Point(e.X, e.Y))

            ResetProjectionText()

            If Not Double.IsInfinity(pnt.X) Then Me.StatusCoords.Text = String.Format(" ({0:N2}; {1:N2})", pnt.X, pnt.Y)

            Dim txt As String = ""
            Static LastPtPoly As Drawing.Point = Drawing.Point.Empty

            If (MapMode = enumMapMode.XSect And e.Button = Windows.Forms.MouseButtons.Left) Or
                MapMode = enumMapMode.PolygonCreate Or
                MapMode = enumMapMode.PolylineCreate Or
                MapMode = enumMapMode.PolygonSelect Or
                MapMode = enumMapMode.PolylineSelect Then
                Select Case MapMode
                    Case enumMapMode.XSect
                        If lstPolygon.Count > 0 Then
                            Dim pt1 As Drawing.Point = GISMap.ProjToPixel(lstPolygon(0)) 'point location
                            Dim pt2 As New Drawing.Point(e.X, e.Y) 'current location of mouse

                            'avoid redrawing when mouse has not moved
                            Static LastPtXSect As Drawing.Point = Drawing.Point.Empty
                            If LastPtXSect.IsEmpty OrElse LastPtXSect.X <> pt2.X OrElse LastPtXSect.Y <> pt2.Y Then
                                'erase prior line and draw new one
                                GISMap.RefreshMap(New Rectangle(pt1.X, pt1.Y, LastPtXSect.X - pt1.X, LastPtXSect.Y - pt1.Y))
                                GISMap.Refresh()
                                Application.DoEvents()
                                Dim p As New Pen(Brushes.Red)
                                p.DashStyle = Drawing2D.DashStyle.Dash
                                p.Width = 3
                                GISMap.CreateGraphics.DrawLine(p, pt1, pt2)
                                Debug.Print(pt1.ToString & "---" & pt2.ToString)
                                LastPtXSect = pt2
                            End If
                        End If
                    Case enumMapMode.PolygonCreate, enumMapMode.PolylineCreate, enumMapMode.PolygonSelect, enumMapMode.PolylineSelect
                        If lstPolygon.Count > 0 Then
                            Dim pt2 As New Drawing.Point(e.X, e.Y) 'current location of mouse
                            'avoid redrawing when mouse has not moved
                            If LastPtPoly.IsEmpty OrElse LastPtPoly.X <> pt2.X OrElse LastPtPoly.Y <> pt2.Y Then

                                Dim ptPoly As New List(Of Drawing.Point)
                                Dim xMin As Integer = pt2.X
                                Dim xMax As Integer = pt2.X
                                Dim yMin As Integer = pt2.Y
                                Dim yMax As Integer = pt2.Y
                                For Each crd In lstPolygon
                                    Dim pt1 As Drawing.Point = GISMap.ProjToPixel(crd)
                                    xMin = Math.Min(xMin, pt1.X)
                                    xMax = Math.Max(xMax, pt1.X)
                                    yMin = Math.Min(yMin, pt1.Y)
                                    yMax = Math.Max(yMax, pt1.Y)
                                    ptPoly.Add(pt1)
                                Next
                                ptPoly.Add(pt2) 'add current location
                                If MapMode = enumMapMode.PolygonCreate Then ptPoly.Add(ptPoly(0)) 'close it

                                'erase prior line and draw new one
                                GISMap.RefreshMap(New Rectangle(xMin, yMin, xMax - xMin, yMax - yMin))
                                GISMap.Refresh()
                                Application.DoEvents()
                                Dim p As New Pen(Brushes.Red)
                                p.DashStyle = Drawing2D.DashStyle.Dash
                                p.Width = 3

                                If MapMode = enumMapMode.PolygonCreate Then
                                    GISMap.CreateGraphics.DrawPolygon(p, ptPoly.ToArray)
                                Else
                                    GISMap.CreateGraphics.DrawLines(p, ptPoly.ToArray)
                                End If
                                LastPtPoly = pt2
                            End If
                        End If
                End Select
            End If

            'if selected items are displayed, don't overwrite with hovered info

            'If (WRDBStationsLayer IsNot Nothing AndAlso WRDBStationsLayer.Selection IsNot Nothing AndAlso WRDBStationsLayer.Selection.Count > 0) OrElse
            '   (AnimationInfo.DataLayer IsNot Nothing AndAlso AnimationInfo.DataLayer.Selection IsNot Nothing AndAlso AnimationInfo.DataLayer.Selection.Count > 0) Then IsActive = False : Exit Sub

            Dim IsHovering As Boolean = False

            If False And GISMap.FunctionMode <> DotSpatial.Controls.FunctionMode.Select Then
                txt = String.Format("Map mode: {0}\n(Use Select mode to display attributes)", GISMap.FunctionMode.ToString)

            ElseIf (ActiveFeatureLayer Is Nothing OrElse Not ActiveFeatureLayer.IsSelected) And
                   (ActiveRasterLayer Is Nothing OrElse Not ActiveRasterLayer.IsSelected) Then
                txt = "No layer is selected."

            ElseIf (ActiveFeatureLayer IsNot Nothing AndAlso Not ActiveFeatureLayer.IsVisible) OrElse
                   (ActiveRasterLayer IsNot Nothing AndAlso Not ActiveRasterLayer.IsVisible) Then
                txt = "Selected layer is not visible."

            ElseIf ActiveRasterLayer IsNot Nothing Then
                txt = String.Format("Layer: {0}\n\n", ActiveRasterLayer.LegendText)
                'get row & column of cursor
                Dim pt = GISMap.PixelToProj(New Drawing.Point(e.X, e.Y))
                With ActiveRasterLayer.DataSet
                    Dim c As Integer = (pt.X - .Xllcenter + .CellWidth / 2) / .CellWidth
                    Dim r As Integer = (.NumRows - 1) - (pt.Y - .Yllcenter - .CellHeight / 2) / .CellHeight
                    If c >= 0 AndAlso c <= .NumColumns - 1 AndAlso
                       r >= 0 AndAlso r <= .NumRows - 1 Then
                        Dim v As Double = .Value(r, c)
                        If v = .NoDataValue Then
                            txt &= String.Format("Raster value: None\n\n", v)
                        Else
                            txt &= String.Format("Raster value: {0:0.000}\n\n", v)
                        End If
                        IsHovering = True
                    End If
                End With


            ElseIf WRDBStationsLayer IsNot Nothing AndAlso
                   ActiveFeatureLayer IsNot Nothing AndAlso
                   ActiveFeatureLayer Is WRDBStationsLayer Then 'AndAlso
                'CType(GISMap.Layers.SelectedLayer, MapPointLayer).DataSet.Features IsNot Nothing Then

                txt = String.Format("Layer: {0}\n", ActiveFeatureLayer.LegendText)

                If WRDBStationsLayer.DataSet.NumRows > 5000 Then
                    txt &= "Too many stations to display"
                Else
                    For Each rec As Integer In FindShapes(e.X, e.Y)
                        With WRDBStationsLayer.DataSet.DataTable.Rows(rec)
                            Select Case Mode
                                Case enumMode.OutputData
                                    If dtResults Is Nothing Then IsActive = False : Exit Sub
                                    Dim sel() As DataRow = dtResults.Select(String.Format("Station_ID='{0}'", CStr(.Item(enumField.ID).Replace("'", ""))))
                                    For i As Integer = 0 To sel.Length - 1
                                        If (Mode <> enumMode.Animate And Mode <> enumMode.OutputData) Or sel(i).Item("MinDate") >= AnimationInfo.ThisDate Then
                                            txt &= String.Format("Station ID: {0}\n{1}\nNo. Data: {2}\nMin Value: {3:N3}\nMax Value: {4:N3}\nAvg Value: {5:N3}\nFirst Date: {6:d}\nLast Date: {7:d}\n------------------------------\n",
                                            .Item(enumField.ID), .Item(enumField.Name), sel(i).Item("NumResult"), sel(i).Item("MinResult"), sel(i).Item("MaxResult"), sel(i).Item("AvgResult"), sel(i).Item("MinDate"), sel(i).Item("MaxDate"))
                                            Exit For
                                        End If
                                    Next
                                Case enumMode.DisplayStations
                                    If WRDBProject.GisQuickTable Then
                                        txt &= String.Format("Station ID: {0}\n{1}\n------------------------------\n",
                                                .Item(enumField.ID), .Item(enumField.Name)).Replace("\n", vbCrLf)
                                    Else
                                        txt &= String.Format("Station ID: {0}\n{1}\nType: {2}\nFreq: {3}\nElev: {4}\n------------------------------\n",
                                                .Item(enumField.ID), .Item(enumField.Name), .Item(enumField.Type), .Item(enumField.Freq), .Item(enumField.Elev))
                                    End If
                                Case enumMode.SelectStations, enumMode.StationsList
                                    txt &= String.Format("{0} - {1}", .Item(enumField.ID), .Item(enumField.Name))
                                    ToolTip.AutomaticDelay = 200
                                    If ToolTip.GetToolTip(GISMap) <> txt Then ToolTip.SetToolTip(GISMap, txt)
                                    IsActive = False
                                    Exit Sub
                                Case enumMode.Animate, enumMode.AreaOfInterest
                                    'don't show anything
                            End Select
                        End With
                        IsHovering = True
                    Next
                End If
                If Mode = enumMode.SelectStations Or Mode = enumMode.StationsList Then ToolTip.Hide(GISMap)

            Else

                txt = String.Format("Layer: {0}\n\n", ActiveFeatureLayer.LegendText)

                'display info from non-stations layer
                If TypeOf GISMap.Layers.SelectedLayer Is FeatureLayer AndAlso
                    CType(GISMap.Layers.SelectedLayer, FeatureLayer).DataSet.Filename <> "" Then
                    Dim lyr As FeatureLayer = ActiveFeatureLayer
                    With lyr.DataSet
                        If .Features.Count < 5000 Then
                            If .FeatureType = DotSpatial.Topology.FeatureType.Polygon Then
                                For i As Integer = 0 To .Features.Count - 1
                                    'If (TypeOf .Features(i).BasicGeometry Is DotSpatial.Topology.Polygon AndAlso CType(.Features(i).BasicGeometry, DotSpatial.Topology.Polygon).Intersects(pnt(0), pnt(1))) OrElse
                                    '    (TypeOf .Features(i).BasicGeometry Is DotSpatial.Topology.MultiPolygon AndAlso CType(.Features(i).BasicGeometry, DotSpatial.Topology.MultiPolygon).Intersects(pnt(0), pnt(1))) Then
                                    If .Features(i).Intersects(pnt) Then
                                        For j As Integer = 0 To .DataTable.Columns.Count - 1
                                            txt &= String.Format("{0}: {1}" & vbCrLf, .DataTable.Columns(j).ColumnName, TestNull(.Features(i).DataRow.Item(j), "<Null>"))
                                        Next
                                        If String.Equals(ActiveFeatureLayer.LegendText, AnimationInfo.DataLayerName, StringComparison.OrdinalIgnoreCase) Then
                                            If AnimationInfo.CurrentResults IsNot Nothing Then
                                                With AnimationInfo
                                                    Select Case .DataLinkageType
                                                        Case enumLinkageType.StationID, enumLinkageType.SegmentNum
                                                            Dim StaID As String = lyr.DataSet.DataTable.Rows(i).Item(.DataAttribute)
                                                            Dim value As Single = 0
                                                            If AnimationInfo.CurrentResults.TryGetValue(StaID, value) Then
                                                                txt &= String.Format("\nCurrent Data Value:\nDate-time: {0:g}\nDisplay: {1}\nAggregation: {2}\nPCode: {3}\nValue: {4:N3}", .ThisDate, cboStatistic.Text, cboAggregation.Text, cboVariable.Text, value)
                                                            End If
                                                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK
                                                            Dim StaID As String = String.Format("{0},{1}", lyr.DataSet.DataTable.Rows(i).Item("I"), lyr.DataSet.DataTable.Rows(i).Item("J"))
                                                            Dim value As Single = 0
                                                            If AnimationInfo.CurrentResults.TryGetValue(StaID, value) Then
                                                                txt &= String.Format("\nCurrent Data Value:\nDate-time: {0:g}\nDisplay: {1}\nAggregation: {2}\nPCode: {3}\nValue: {4:N3}", .ThisDate, cboStatistic.Text, cboAggregation.Text, cboVariable.Text, value)
                                                            End If
                                                    End Select
                                                End With
                                            End If
                                            If AnimationInfo.DataShowVectors AndAlso AnimationInfo.CurrentVectors IsNot Nothing Then
                                                With AnimationInfo
                                                    Dim StaID As String = lyr.DataSet.DataTable.Rows(i).Item(.DataAttribute)
                                                    If AnimationInfo.CurrentVectors.ContainsKey(StaID) Then
                                                        Dim value As Single = AnimationInfo.CurrentVectors(StaID).Item1
                                                        Dim angle As Single = AnimationInfo.CurrentVectors(StaID).Item2
                                                        txt &= String.Format("\n\nPCodeX: {0}\nPCodeY: {1}\nMagnitude: {2:N3}\nAngle: {3:N3}", .DataXParm, .DataYParm, value, angle)
                                                    End If
                                                End With
                                            End If
                                        End If
                                        IsHovering = True
                                        Exit For
                                    End If
                                Next
                            ElseIf .FeatureType = DotSpatial.Topology.FeatureType.Point Or .FeatureType = DotSpatial.Topology.FeatureType.Line Then
                                For i As Integer = 0 To .Features.Count - 1
                                    Const WindowSize As Integer = 5
                                    Dim pnt1 As DotSpatial.Topology.Coordinate = GISMap.PixelToProj(New Drawing.Point(e.X - WindowSize, e.Y - WindowSize))
                                    Dim pnt2 As DotSpatial.Topology.Coordinate = GISMap.PixelToProj(New Drawing.Point(e.X + WindowSize, e.Y + WindowSize))
                                    Dim env As New DotSpatial.Topology.Envelope(pnt1, pnt2)

                                    If .Features(i).Intersects(env) Then
                                        For j As Integer = 0 To .DataTable.Columns.Count - 1
                                            txt &= String.Format("{0}: {1}" & vbCrLf, .DataTable.Columns(j).ColumnName, .Features(i).DataRow.Item(j))
                                        Next
                                        IsHovering = True
                                        Exit For
                                    End If
                                Next
                            End If
                        End If
                    End With

                ElseIf TypeOf GISMap.Layers.SelectedLayer Is RasterLayer Then
                    Dim lyr As RasterLayer = GISMap.Layers.SelectedLayer
                    With lyr.DataSet
                        Dim rc As DotSpatial.Data.RcIndex = .ProjToCell(pnt(0), pnt(1))
                        If rc.Row >= 0 AndAlso rc.Row <= .NumRows - 1 AndAlso rc.Column >= 0 AndAlso rc.Column <= .NumColumns - 1 Then
                            txt &= "Cell Value: " & Math.Round(lyr.DataSet.Value(rc.Row, rc.Column), 4)
                            IsHovering = True
                        End If
                    End With

                End If

            End If

            If IsHovering Then
                If txt <> lasttxt Then
                    txtInfo.Text = txt.Replace("\n", vbCrLf)
                    lasttxt = txt
                End If
            Else
                UpdateSelectionList()
                lasttxt = ""
            End If

            IsActive = False

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub GISMap_MouseLeave(sender As Object, e As System.EventArgs) Handles GISMap.MouseLeave
        StatusCoords.Text = ""
    End Sub

    Private Sub GISMap_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs) Handles GISMap.SelectionChanged
        If AnimationInfo.IsAnimating Then tbAnimPause.PerformClick() 'stop the animation
        UpdateSelectionList()
    End Sub

    Private Sub UpdateSelectionList()
        Try
            Static lasttxt As String = ""
            Dim txt As String = ""
            With GISMap
                lstStations.Clear()
                If WRDBStationsLayer IsNot Nothing AndAlso WRDBStationsLayer.Selection IsNot Nothing AndAlso WRDBStationsLayer.Selection.Count > 0 Then
                    txt = "Selected Stations:"
                    For Each rownum As Integer In CType(WRDBStationsLayer.Selection, DotSpatial.Symbology.IndexSelection)
                        Dim StationID As String = TestNull(WRDBStationsLayer.DataSet.DataTable.Rows(rownum)(enumField.ID), "")
                        Dim Stat_Name As String = TestNull(WRDBStationsLayer.DataSet.DataTable.Rows(rownum)(enumField.Name), "")
                        Dim Stat_Type As String = TestNull(WRDBStationsLayer.DataSet.DataTable.Rows(rownum)(enumField.Type), "")
                        If Mode <> enumMode.SelectStations Or cboStationType.SelectedIndex = 0 Or cboStationType.Text = Stat_Type Then
                            lstStations.Add(StationID)
                            txt &= vbCrLf & StationID & ": " & Stat_Name
                        End If
                    Next
                ElseIf AnimationInfo.DataLayer IsNot Nothing AndAlso AnimationInfo.DataLayer.Selection IsNot Nothing AndAlso AnimationInfo.DataLayer.Selection.Count > 0 Then
                    txt = "Selected Stations:"
                    For Each rownum As Integer In CType(AnimationInfo.DataLayer.Selection, DotSpatial.Symbology.IndexSelection)
                        Dim StationID As String = ""
                        With AnimationInfo.DataLayer.DataSet.DataTable.Rows(rownum)
                            Select Case AnimationInfo.DataLinkageType
                                Case enumLinkageType.StationID, enumLinkageType.SegmentNum
                                    StationID = TestNull(.Item(AnimationInfo.DataAttribute), "")
                                Case Else
                                    StationID = String.Format("{0},{1}", TestNull(.Item("I"), ""), TestNull(.Item("J"), ""))
                            End Select
                        End With
                        Dim Stat_Name As String = "Unknown"
                        Dim Stat_Type As String = "Unknown"
                        If True Then
                            lstStations.Add(StationID)
                            txt &= vbCrLf & StationID & ": " & Stat_Name
                        End If
                    Next
                Else
                    If ActiveFeatureLayer IsNot Nothing Then txt = "Selected items: " & ActiveFeatureLayer.Selection.Count
                End If

                If txt <> txtInfo.Text Then
                    txtInfo.Text = txt.Replace("\n", vbCrLf)
                    lasttxt = txt
                End If

                If ModeSettings.AllowStationSelection Then btnSelect.Visible = lstStations.Count > 0
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            ProgressComplete()
        End Try
    End Sub

    Private Sub GISMap_ProjectionChanged(sender As Object, e As System.EventArgs) Handles GISMap.ProjectionChanged
        ResetProjectionText()
    End Sub

    Private Sub ResetProjectionText()
        If GISMap.Projection Is Nothing Then Exit Sub
        With GISMap.Projection
            'don't want to use ToString because the text is so long
            Dim Name As String = "Unknown"
            Dim Units As String = "Unknown"
            If Not String.IsNullOrEmpty(.Name) Then
                Name = .Name.Replace("_", " ")
            ElseIf Not String.IsNullOrEmpty(.GeographicInfo.Name) Then
                Name = .GeographicInfo.Name.Replace("_", " ")
            End If
            If Not String.IsNullOrEmpty(.Unit.Name) Then
                Units = " (" & .Unit.Name.Replace("_", " ") & ")"
            End If
            If .Equals(DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984) Then Units = "" 'incorrectly shows as meters
            Me.StatusProjection.Text = String.Format("{0}{1}", Name, Units)
        End With
    End Sub

#End Region

    Public Sub moveLayerTop()
        moveLayerTop(GISMap.Layers.SelectedLayer)
    End Sub

    ''' <summary>
    ''' Move specified layer to top of list (last in set of layers)
    ''' </summary>
    Public Sub moveLayerTop(Layer As IMapLayer)
        With GISMap
            moveLayerTop(Layer, .MapFrame)
            For Each grp In .GetAllGroups
                moveLayerTop(Layer, grp)
            Next
            .Refresh()
        End With
    End Sub

    Private Sub moveLayerTop(Layer As IMapLayer, Group As IMapGroup)
        With Group
            If .Layers.IndexOf(Layer) <> -1 Then
                .Layers.SuspendEvents()
                'note: removing layer disposes of some part of it, so each layer needs to be locked
                Layer.LockDispose()
                If .Layers.Remove(Layer) Then
                    .Layers.Add(Layer)
                    .Layers.SelectedLayer = Layer
                End If
                Layer.UnlockDispose()
                .Layers.SelectedLayer = Layer
                .Layers.ResumeEvents()
            End If
        End With
    End Sub

    Public Sub moveLayerBottom()
        moveLayerBottom(GISMap.Layers.SelectedLayer)
    End Sub

    ''' <summary>
    ''' Move specified layer to top of list (last in set of layers)
    ''' </summary>
    Public Sub moveLayerBottom(Layer As IMapLayer)
        With GISMap
            moveLayerBottom(Layer, .MapFrame)
            For Each grp In .GetAllGroups
                moveLayerBottom(Layer, grp)
            Next
            .Refresh()
        End With
    End Sub

    Private Sub moveLayerBottom(Layer As IMapLayer, Group As IMapGroup)
        With Group
            If .Layers.IndexOf(Layer) <> -1 Then
                .Layers.SuspendEvents()
                'note: removing layer disposes of some part of it, so each layer needs to be locked
                Layer.LockDispose()
                If .Layers.Remove(Layer) Then
                    .Layers.Insert(0, Layer)
                    .Layers.SelectedLayer = Layer
                End If
                Layer.UnlockDispose()
                .Layers.SelectedLayer = Layer
                .Layers.ResumeEvents()
            End If
        End With
    End Sub

    Public Sub moveLayerUp()
        moveLayerUp(GISMap.Layers.SelectedLayer)
    End Sub

    Public Sub moveLayerUp(Layer As IMapLayer)
        With GISMap
            moveLayerUp(Layer, .MapFrame)
            For Each grp In .GetAllGroups
                moveLayerUp(Layer, grp)
            Next
            .Refresh()
        End With
    End Sub

    Private Sub moveLayerUp(Layer As IMapLayer, Group As IMapGroup)
        With Group
            Dim index As Integer = .Layers.IndexOf(Layer)
            If index <> -1 And index < .Layers.Count - 1 Then
                Layer.LockDispose()
                .Layers.Remove(Layer)
                .Layers.SuspendEvents()
                .Layers.Insert(index + 1, Layer)
                Layer.UnlockDispose()
                .Layers.SelectedLayer = Layer
                .Layers.ResumeEvents()
            End If
        End With
    End Sub

    Public Sub moveLayerDown()
        moveLayerDown(GISMap.Layers.SelectedLayer)
    End Sub

    Public Sub moveLayerDown(Layer As IMapLayer)
        With GISMap
            moveLayerDown(Layer, .MapFrame)
            For Each grp In .GetAllGroups
                moveLayerDown(Layer, grp)
            Next
            .Refresh()
        End With
    End Sub

    Private Sub moveLayerDown(Layer As IMapLayer, Group As IMapGroup)
        With Group
            Dim index As Integer = .Layers.IndexOf(Layer)
            If index > 0 Then
                Layer.LockDispose()
                .Layers.Remove(Layer)
                .Layers.SuspendEvents()
                .Layers.Insert(index - 1, Layer)
                Layer.UnlockDispose()
                .Layers.SelectedLayer = Layer
                .Layers.ResumeEvents()
            End If
        End With
    End Sub

    Private Sub LocatePlace()
        Try
            If txtLocation.Text = "" Or WRDBStationsLayer Is Nothing Then Exit Sub
            With WRDBStationsLayer.DataSet
                Dim found As Boolean = False
                For i As Integer = 0 To .Features.Count - 1
                    If .Features(i).DataRow("Station_ID") = txtLocation.Text.Trim.ToUpper Then
                        found = True
                        Dim x As Single = .Features(i).Coordinates(0).X
                        Dim y As Single = .Features(i).Coordinates(0).Y
                        Dim MyExtents As DotSpatial.Data.Extent = GISMap.ViewExtents
                        With MyExtents
                            Dim xy() As Double = {x, y}
                            Dim z() As Double = {0}
                            DotSpatial.Projections.Reproject.ReprojectPoints(xy, z, DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984, ProjectProjection, 0, 1)

                            .SetValues(xy(0) - .Width / 2, xy(1) - .Height / 2, xy(0) + .Width / 2, xy(1) + .Height / 2)
                            GISMap.ViewExtents = MyExtents
                        End With
                        'select that point
                        WRDBStationsLayer.SelectByAttribute(String.Format("Station_ID='{0}'", txtLocation.Text.Trim.ToUpper))
                        Exit For
                    End If
                Next
                If Not found Then
                    WarningMsg("Station ID {0} was not found.", txtLocation.Text)
                End If
            End With
            txtLocation.Text = ""
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuClose.Click
        Close()
    End Sub

    Private Sub mnuData_DropDownOpening(sender As Object, e As EventArgs) Handles mnuData.DropDownOpening
        If Mode = enumMode.Animate Then
            'disable most items if data file not opened yet
            Dim tf As Boolean = AnimationInfo.dsData IsNot Nothing
            mnuDataAggregation.Visible = tf
            mnuDataClose.Visible = tf
            mnuDataConfig.Visible = tf
            mnuDataDateTime.Visible = tf
            mnuDataStatistic.Visible = tf
            mnuDataOptions.Visible = tf
            mnuDataVariable.Visible = tf
            mnuDataLayer.Visible = tf
            mnuDataSepOpen.Visible = tf
            mnuDataSepOptions.Visible = tf
        End If
    End Sub

    Private Sub mnuView_DropDownOpening(sender As Object, e As EventArgs) Handles mnuView.DropDownOpening
        'disable data items if data file not opened yet
        Dim tf As Boolean = AnimationInfo.dsData IsNot Nothing
        mnuViewData.Enabled = tf
        mnuViewLegend.Enabled = tf
        mnuViewLabels.Enabled = WRDBStationsLayer IsNot Nothing
    End Sub

    Private Sub mnuDataAggregate_DropDownItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles mnuDataAggregation.DropDownItemClicked
        Try
            If e.ClickedItem Is mnuAggregateNone Then
                Aggregate = enumAggregate.None
            ElseIf e.ClickedItem Is mnuAggregatePOR Then
                Aggregate = enumAggregate.POR
            ElseIf e.ClickedItem Is mnuAggregateDay Then
                Aggregate = enumAggregate.Day
            ElseIf e.ClickedItem Is mnuAggregateMonth Then
                Aggregate = enumAggregate.Month
            ElseIf e.ClickedItem Is mnuAggregateGrowing Then
                Aggregate = enumAggregate.Growing
            ElseIf e.ClickedItem Is mnuAggregateAnnual Then
                Aggregate = enumAggregate.Annual
            End If
            tblAnim.Enabled = Aggregate <> enumAggregate.POR
            mnuViewAnimate.Enabled = Aggregate <> enumAggregate.POR
            mnuViewAnimateSpeed.Enabled = Aggregate <> enumAggregate.POR
            CheckDropDownItem(mnuDataAggregation, e.ClickedItem)
            cboAggregation.Text = e.ClickedItem.Text.Replace("&", "")
            If Mode = enumMode.OutputData Then
                SummarizeStationData()
            Else
                ResetAnimation()
            End If
            RefreshStationsDisplay()
            ProgressComplete()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuDataDisplay_DropDownItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles mnuDataStatistic.DropDownItemClicked
        Me.Refresh()
        Application.DoEvents()
        cboStatistic.Text = e.ClickedItem.Text
        CheckDropDownItem(mnuDataStatistic, e.ClickedItem)
    End Sub

    Private Sub mnuDataPCode_DropDownItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles mnuDataVariable.DropDownItemClicked
        Application.DoEvents()
        cboVariable.Text = e.ClickedItem.Text
        CheckDropDownItem(mnuDataStatistic, e.ClickedItem)
    End Sub

    Private Sub mnuFileClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDataClose.Click, tbClose.Click
        Try
            With AnimationInfo
                If .dsData IsNot Nothing AndAlso WarningMsg("Are you sure you want to close the data table and detach it from the shape file?") = Windows.Forms.DialogResult.OK Then
                    cboVariable.Text = "None" 'want to remove scheme
                    RefreshPolygons()
                    If .DataVectorLayer IsNot Nothing Then GISMap.Layers.Remove(.DataVectorLayer)
                    .DataVectorLayer = Nothing
                    .dsData.Dispose()
                    .dsData = Nothing
                    .DataLayerName = ""
                    .DataAttribute = ""
                    .DataHandle = -1
                    If .CurrentResults IsNot Nothing Then .CurrentResults.Clear()
                    .CurrentResults = Nothing
                    If .CurrentVectors IsNot Nothing Then .CurrentVectors.Clear()
                    .CurrentVectors = Nothing
                    picLegend.Visible = False
                    mnuViewLegend.Checked = False
                    tblData.Visible = False
                    cboVariable.Items.Clear()

                    tbClose.Enabled = False
                    tbLegend.Enabled = False
                    tbDataOptions.Enabled = False
                    tbDataTable.Enabled = False

                    GC.Collect()
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuFileNew_Click(sender As Object, e As System.EventArgs) Handles mnuFileNew.Click
        With GISMap
            If IsModified And GISMap.Layers.Count > 0 AndAlso Not (GISMap.Layers.Count = 1 AndAlso WRDBStationsLayer IsNot Nothing) Then
                Select Case SaveChangesWarning(GISFileName)
                    Case Windows.Forms.DialogResult.Yes : If Not SaveSettings(GISFileName) Then Exit Sub
                    Case Windows.Forms.DialogResult.No
                    Case Windows.Forms.DialogResult.Cancel : Exit Sub
                End Select
            End If
            WRDBStationsLayer = Nothing
            .Layers.Clear()
            SetBasemap("None")
            picLegend.Visible = False
            ProjectProjection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
            GISFileName = ""
            ResetTitleBar()
            With AnimationInfo
                .CurrentVectors = Nothing
                .DataLayerName = ""
                .DataAttribute = ""
                .DataHandle = -1
                .DataShowVectors = False
                .DataXParm = ""
                .DataYParm = ""
                .DataArrowLength = 0
                .DataArrowColor = Color.Black
                If .dsData IsNot Nothing Then .dsData.Dispose()
                .dsData = Nothing
            End With
            tblData.Visible = False
        End With
    End Sub

    Private Sub mnuFileOpen_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuDataOpen.Click, tbOpen.Click, mnuDataOpenTable.Click
        Try
            If GISMap.Layers.Count = 0 Then
                WarningMsg("You cannot open a data file until you open a GIS polygon shape file to which the data will be mapped.")
                Exit Sub
            End If
            Dim FileName As String = ""
            If sender Is mnuDataOpenTable Then
                'this has not been implemented yet
            Else
                With New OpenFileDialog
                    .AddExtension = True
                    .CheckFileExists = True
                    .CheckPathExists = True
                    .DefaultExt = ".bmd"
                    .Filter = "All Data Formats|*.bmd;*.bmd2;*.db;*.txt;*.csv;*.dbf;*.mdb;*.sdb;*.xls*;*.sdb;*.fdb;*.sdf;*.out;*.air|" &
                              "Binary Modeling Data Files|*.bmd|" &
                              "Binary Modeling Data Files Ver 2|*.bmd2|" &
                              "Paradox Tables|*.db|" &
                              "Tab-delimited Text Files|*.txt|" &
                              "Comma-separated Text Files|*.csv|" &
                              "dBase Tables|*.dbf|" &
                              "Access Database Files|*.mdb|" &
                              "SQLite Database Files|*.sdb|" &
                              "Excel Spreadsheet Files|*.xls*|" &
                              "Firebird Database Files|*.fdb|" &
                              "SQL Server CE Database Files|*.sdf|" &
                              "LSPC Output Files (*.out)|*.out|" &
                              "LSPC Input Files (*.air)|*.air|" &
                              "EFDC Output Files (*.out)|*.out"
                    .FilterIndex = GetSetting(REGAPP, Me.Name, "FileFilter", 0)
                    .InitialDirectory = GetSetting(REGAPP, "General", "LastOpenDir", "")
                    .Title = "Open Data File"
                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                        SaveSetting(REGAPP, Me.Name, "FileFilter", .FilterIndex)
                        If Not My.Computer.FileSystem.FileExists(.FileName) Then .Dispose() : Exit Sub
                        FileName = .FileName
                    Else
                        .Dispose()
                        Exit Sub
                    End If
                End With
            End If
            SaveSetting(REGAPP, "General", "LastOpenDir", IO.Path.GetDirectoryName(FileName))

            With AnimationInfo
                If Not AssignData(FileName, "", "", enumLinkageType.StationID,
                                  AnimationInfo.DataShowVectors, AnimationInfo.DataXParm, AnimationInfo.DataYParm, AnimationInfo.DataArrowLength, AnimationInfo.DataArrowColor, True) Then
                    .DataLayerName = ""
                    .DataAttribute = ""
                    .DataField = ""
                    .DataHandle = -1
                    .CurrentVectors = Nothing
                    .DataShowVectors = False
                    .DataXParm = ""
                    .DataYParm = ""
                    .DataArrowLength = 0
                    .DataArrowColor = Color.Black
                    If .dsData IsNot Nothing Then .dsData.Dispose()
                    .dsData = Nothing
                    Exit Sub
                End If
            End With

            tbClose.Enabled = True
            tbLegend.Enabled = True
            tbDataOptions.Enabled = True
            tbDataTable.Enabled = True


        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuDataConfig_Click(sender As Object, e As EventArgs) Handles mnuDataConfig.Click
        With AnimationInfo
            If .dsData Is Nothing Then
                WarningMsg("You cannot reconfigure a data file until you open it.")
                Exit Sub
            End If
            AssignData(.dsData.FileName, .DataLayerName, .DataAttribute, enumLinkageType.StationID, .DataShowVectors, .DataXParm, .DataYParm, .DataArrowLength, .DataArrowColor, True)
        End With
    End Sub

    ''' <summary>
    ''' Open data source and set up linkages from Station ID to named field in polygon shape file; prepare polygon data table to receive data
    ''' </summary>
    ''' <param name="DataFileName">Name of data source file</param>
    ''' <param name="LayerName">Name of polygon layer</param>
    ''' <param name="FieldName">Name of field in polygon layer to which Station ID will be mapped</param>
    ''' <param name="FieldContents">Description of the contents of the mapping field</param>
    ''' <returns>True if successful</returns>
    Private Function AssignData(DataFileName As String, LayerName As String, FieldName As String, FieldContents As enumLinkageType, DrawVectors As Boolean, XParm As String, YParm As String, ArrowLength As Double, ArrowColor As Color, Optional ShowDialog As Boolean = False) As Boolean
        Try
            With AnimationInfo
                WriteProgress("Initializing data file...")
                .dsData = WRDB.DataSource.clsData.Instantiate(DataFileName)
                If .dsData Is Nothing Then Return Nothing
                ProgressComplete()

                If Not (.dsData.ContainsFieldName("Station_ID") And .dsData.ContainsFieldName("Date_Time")) Then
                    WarningMsg("The selected data file does not contain the required fields: Station_ID and Date_Time.")
                    Return False
                End If

                Dim CheckShapes As Boolean = False

                If LayerName = "" Or ((.DataLinkageType = enumLinkageType.StationID Or .DataLinkageType = enumLinkageType.SegmentNum) And FieldName = "") Or ShowDialog Then
                    Dim frm = New frmSelectLayer(GISMap)
                    With frm
                        If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                            CheckShapes = True
                            .Dispose()
                        Else
                            .Dispose()
                            Return False
                        End If
                    End With
                Else
                    .DataLayerName = LayerName
                    .DataLayer = GISForm.GetLayer(.DataLayerName)
                End If

                If .DataLayer Is Nothing Then Return False

                'need to unload and reload this layer in case it is being assigned a new data table (attributes seem to get corrupted)
                'Dim fn As String = CType(GISMap.Layers(.DataHandle), DotSpatial.Controls.MapPolygonLayer).DataSet.Filename
                'Dim lyr As MapPolygonLayer = GISMap.Layers(.DataHandle)
                'lyr.LockDispose()
                'GISMap.Layers.Remove(lyr)
                'lyr.LegendText = .DataLayerName

                .DataLayer.LockDispose()
                GISMap.Layers.Remove(.DataLayer)
                GISMap.Layers.Insert(IIf(.DataHandle = -1, 0, .DataHandle), .DataLayer)
                .DataLayer.UnlockDispose()

                cboVariable.Items.Clear()
                mnuDataVariable.DropDownItems.Clear()
                cboVariable.Items.Add("None")
                cboVariable.Items.AddRange(AnimationInfo.dsData.PCodes.ToArray)
                For Each s As String In AnimationInfo.dsData.PCodes
                    mnuDataVariable.DropDownItems.Add(s)
                    If mnuDataVariable.DropDownItems.Count > 50 Then Exit For
                Next

                'see what timestep is appropriate
                If .dsData.Read Then
                    Dim dt1 As Date = .dsData.Items("Date_Time")
                    If .dsData.Read Then
                        Dim dt2 As Date = .dsData.Items("Date_Time")
                        If dt2.Subtract(dt1).TotalHours <= 1 Then
                            .TimeUnits = enumTimeUnits.Minute
                            .TimeStep = dt2.Subtract(dt1).TotalMinutes
                        ElseIf dt2.Subtract(dt1).TotalDays <= 1 Then
                            .TimeUnits = enumTimeUnits.Hour
                            .TimeStep = dt2.Subtract(dt1).TotalHours
                        Else
                            .TimeUnits = enumTimeUnits.Day
                            .TimeStep = dt2.Subtract(dt1).TotalDays
                        End If
                    End If
                    .dsData.Rewind()
                End If

                'this is list of data stations that have associated shapes on data layer
                .StationList = New Generic.List(Of String)

                'get list of unique station IDs or segment numbers from data layer

                If .DataLayer IsNot Nothing Then

                    Select Case .DataLinkageType
                        Case enumLinkageType.StationID, enumLinkageType.SegmentNum 'either Station_ID or SEGID
                            Dim fld As Integer = GetFieldIndex(.DataLayer.DataSet.DataTable, .DataAttribute)
                            If fld <> -1 Then
                                For i As Integer = 0 To .DataLayer.DataSet.Features.Count - 1
                                    Dim StaID As String = TestNull(.DataLayer.DataSet.DataTable.Rows(i)(fld), "")
                                    If Not .StationList.Contains(StaID) Then .StationList.Add(StaID)
                                Next
                            End If
                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK 'get I, J and K from separate hardwired attributes I, J, and K
                            For i As Integer = 0 To .DataLayer.DataSet.Features.Count - 1
                                Dim IJK As String = String.Format("{0},{1}", TestNull(.DataLayer.DataSet.DataTable.Rows(i)("I"), ""), TestNull(.DataLayer.DataSet.DataTable.Rows(i)("J"), ""))
                                If cboLayer.Text <> "" Then IJK &= "," & Val(cboLayer.Text)
                                If Not .StationList.Contains(IJK) Then .StationList.Add(IJK)
                            Next
                    End Select

                    .StationList.Sort()

                    'if the user entered a 0 for the arrow length, try to automatically determine one by getting an average polygon size

                    If .DataArrowLength = 0 Then
                        Dim sum As Double = 0
                        For Each f As Feature In .DataLayer.DataSet.Features
                            sum += Math.Sqrt(f.Envelope.Width ^ 2 + f.Envelope.Height ^ 2)
                        Next
                        .DataArrowLength = sum / .DataLayer.DataSet.Features.Count
                    End If

                    'now, modify data feature layer data table to include fields into which the values can be placed for thematic mapping

                    If Not .DataLayer.DataSet.AttributesPopulated Then .DataLayer.DataSet.FillAttributes()
                    If Not .DataLayer.DataSet.DataTable.Columns.Contains("Result") Then 'not found, add it
                        .DataLayer.DataSet.DataTable.Columns.Add("Result", GetType(Double))
                        .DataLayer.DataSet.Save()
                    End If
                    '.DataLayer.DataSet.DataTable.PrimaryKey = New DataColumn() {.DataLayer.DataSet.DataTable.Columns(.DataField)}

                    Select Case AnimationInfo.DataLinkageType
                        Case enumLinkageType.StationID, enumLinkageType.SegmentNum
                            cboLayer.Items.Clear()
                            mnuDataLayer.DropDownItems.Clear()
                            cboLayer.SelectedIndex = -1
                            cboLayer.Enabled = False
                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK
                            cboLayer.Items.Clear()
                            mnuDataLayer.DropDownItems.Clear()
                            For Each lyr As Integer In .dsData.Layers
                                cboLayer.Items.Add(lyr)
                                mnuDataLayer.DropDownItems.Add(lyr)
                            Next
                            cboLayer.SelectedIndex = 0
                            cboLayer.Enabled = True
                    End Select

                    CheckDropDownItem(mnuDataLayer, cboLayer.SelectedIndex)

                    Dim tf As Boolean = cboLayer.Items.Count > 0
                    mnuDataLayer.Enabled = tf
                    cboLayer.Enabled = tf
                    lblLayer.Enabled = tf

                End If

                If CheckShapes Then

                    'get date range
                    .MinDate = .dsData.MinDate
                    .MaxDate = .dsData.MaxDate

                    'get list of unique station IDs from data file and see how many matches there are to shape file
                    Dim lstStaData As New Generic.List(Of String)
                    Select Case .DataLinkageType
                        Case enumLinkageType.SegmentNum 'just add indices available in BMD data file (1 to N)
                            For i As Integer = 1 To .dsData.StationIDs.Count
                                lstStaData.Add(i)
                            Next
                        Case enumLinkageType.StationID
                            lstStaData = .dsData.StationIDs
                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK  'format will be like I=iiiJ=jjjK=kk or I=iii J=jjj
                            For i As Integer = 1 To .dsData.StationIDs.Count - 1
                                If .dsData.StationIDs(i).StartsWith("I=") Then
                                    Dim ar() As String = .dsData.StationIDs(i).Split(New Char() {"="})
                                    If ar.Length >= 3 Then
                                        Dim ij As String = String.Format("{0},{1}", Val(ar(1)), Val(ar(2)))
                                        If cboLayer.Items.Count > 0 Then ij &= "," & Val(cboLayer.Text)
                                        If Not lstStaData.Contains(ij) Then lstStaData.Add(ij)
                                    End If
                                End If
                            Next
                    End Select

                    'find number of missing shapes
                    Dim NumMissingShapes As Integer = 0
                    For Each StaID As String In lstStaData
                        If Not .StationList.Contains(StaID) Then NumMissingShapes += 1
                    Next

                    'find number of missing data stations
                    Dim NumMissingStations As Integer = 0
                    For Each StaID As String In .StationList
                        If Not lstStaData.Contains(StaID) Then NumMissingStations += 1
                    Next

                    If NumMissingShapes + NumMissingStations > 0 Then WarningMsg("There are some mismatches between the data layer shapefile and available Station IDs in the data file: \n\n\t\b {0} out of the total of {1} unique Station IDs in the data file are missing from the shape file\n\n\t\b {2} out of the total of {3} shapes in the data layer had no associated data stations\n\nAs a result, some shapes in the data layer will not be visible during animation.", NumMissingShapes, lstStaData.Count, NumMissingStations, .StationList.Count)
                    If cboVariable.Items.Count > 0 Then cboVariable.SelectedIndex = 0
                End If

                ActiveFeatureLayer = GetLayer(.DataLayerName)
            End With
            If cboVariable.Text <> "None" Then
                picLegend.Visible = True
                mnuViewLegend.Checked = True
            End If
            tblData.Visible = True
            tblAnim.Enabled = cboAggregation.Text <> "Period of Record" And cboVariable.Text <> "None"
            tbClose.Enabled = True
            tbLegend.Enabled = True
            tbDataOptions.Enabled = True
            tbDataTable.Enabled = True
            Return True
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        Finally
            ProgressComplete()
        End Try
    End Function

    Private Sub mnuFileOpenGroup_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuFileOpenGroup.Click, tbOpenGroup.Click, mnuFileAddGroup.Click
        Try
            Dim IsAppend As Boolean = sender Is mnuFileAddGroup

            'load last layers used
            If IsModified And Not IsAppend Then
                Select Case SaveChangesWarning(GISFileName)
                    Case Windows.Forms.DialogResult.Yes : If Not SaveSettings(GISFileName) Then Exit Sub
                    Case Windows.Forms.DialogResult.Cancel : Exit Sub
                End Select
            End If
            With New OpenFileDialog
                .AddExtension = True
                .CheckFileExists = True
                .CheckPathExists = True
                .DefaultExt = ".gis"
                Dim s As String = "WRDB GIS File|*.gis" & IIf(IsAppend, "", "|MapWindow " & GISAppManager.SerializationManager.SaveDialogFilterText)
                .Filter = s
                .FilterIndex = 0
                .Title = IIf(IsAppend, "Append", "Load") & " GIS File"
                .ValidateNames = True
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    If Not IsAppend Then GISFileName = .FileName
                    ResetTitleBar()
                    .Dispose()
                Else
                    Exit Sub
                    .Dispose()
                End If
                LoadSettings(.FileName, IsAppend)
            End With

            GISMap.Layers.ResumeEvents()
            GISLegend.RefreshNodes()
            GISLegend.ResetScroll()

            ResetAnimation()
            RefreshStationsDisplay()

            ' Call sub to add this file as an MRU
            If Not IsAppend Then mru.AddFile(GISFileName)
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuFileOpenLast_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuFileOpenLast.Click, tbOpenLast.Click
        Try
            If mnuFileRecent.DropDownItems.Count > 0 Then mnuFileRecent.DropDownItems(0).PerformClick()
            'LoadSettings(ModeSettings.LastSettingsFilename)
            'If Mode = enumMode.Animate Then SummarizePolygonData()
            'ResetAnimation()
            'ResetTrackbar()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuFileSaveGroup_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuFileSaveGroup.Click, tbSave.Click
        SaveSettings(GISFileName)
    End Sub

    Private Sub mnuFileSaveGroupAs_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuFileSaveGroupAs.Click, tbSaveAs.Click
        If SaveSettings(GISFileName, True) Then mru.AddFile(GISFileName)
    End Sub

    Private Sub mnuHelp_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuHelpAbout.Click, mnuHelpContents.Click, mnuHelpEMail.Click, mnuHelpWeb.Click, mnuHelpWhatsThis.Click
        Try
            Select Case mnuHelp.DropDownItems.IndexOf(sender)
                Case 0 : ShowHelp(Me, 24)
                Case 1 : VB6.WhatsThisMode(Me)
                Case 3 : Process.Start("http://www.wrdb.com")
                Case 4 : Process.Start("mailto:cwilson@wileng.com")
                Case 6
                    With New frmAbout
                        .ShowDialog(Me)
                        .Dispose()
                    End With
                    'VB6.ShowForm(frmAbout, VB6.FormShowConstants.Modal, Me)
                Case Else
                    WarningMsg("This is not yet implemented.")
            End Select
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuLayerAdd.Click, mnuLayerAdd2.Click, tbLayerAdd.Click
        Try
            With New OpenFileDialog
                .InitialDirectory = GetSetting(REGAPP, Me.Name, "AddLayer", LayerDirectory)
                .Filter = DotSpatial.Data.DataManager.DefaultDataManager.DialogReadFilter
                .AddExtension = True
                .CheckFileExists = True
                .DefaultExt = ".shp"
                .Multiselect = True
                .Title = "Select GIS Layer(s) to Add"
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    SaveSetting(REGAPP, Me.Name, "AddLayer", IO.Path.GetDirectoryName(.SafeFileName))
                    For Each s As String In .FileNames
                        Dim MapGroup As IMapGroup = Nothing
                        If GISMap.Layers.SelectedLayer IsNot Nothing Then
                            If TypeOf GISMap.Layers.SelectedLayer Is MapGroup Then
                                MapGroup = GISMap.Layers.SelectedLayer
                            ElseIf GISMap.Layers.SelectedLayer.LegendType = LegendType.Layer Then
                                MapGroup = GISMap.Layers.SelectedLayer.GetParentItem
                            End If
                        End If
                        If MapGroup Is Nothing Then MapGroup = GISMap.MapFrame
                        AddLayer(s, MapGroup)
                    Next
                End If
                .Dispose()
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerDown_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuLayerDown.Click, mnuLayerDown2.Click, tbLayerDown.Click
        Try
            If GISMap.Layers.SelectedLayer IsNot Nothing Then
                moveLayerDown()
                IsModified = True
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerRemove_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuLayerRemove.Click, mnuLayerRemove2.Click, tbLayerRemove.Click
        Try
            If GISMap.Layers.SelectedLayer IsNot Nothing Then
                GISMap.Layers.Remove(GISMap.Layers.SelectedLayer)
                GISLegend.RefreshNodes()
                'if have removed all layers, reset project projection to lat/long
                If GISMap.Layers.Count = 0 Then ProjectProjection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
                IsModified = True
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerRemoveAll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuLayerRemoveAll.Click, mnuLayerRemoveAll2.Click, tbRemoveAll.Click
        Try
            WRDBStationsLayer = Nothing
            GISMap.Layers.Clear()
            GISLegend.RefreshNodes()
            SetBasemap("None")
            'if have removed all layers, reset project projection to lat/long
            'ProjectProjection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
            IsModified = True
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerTop_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuLayerTop.Click, mnuLayerTop2.Click, tbLayerTop.Click
        Try
            moveLayerTop()
            IsModified = True
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerBot_Click(sender As Object, e As EventArgs) Handles mnuLayerBot.Click, mnuLayerBot2.Click, tbLayerBot.Click
        Try
            moveLayerBottom()
            IsModified = True
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuLayerUp_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuLayerUp.Click, mnuLayerUp2.Click, tbLayerUp.Click
        Try
            moveLayerUp()
            IsModified = True
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    'deprecated 9/1/16 because D4EM uses DS 1.7 and is not compatible with DS 1.9. Seldom used feature; layers can be downloaded with BASINS or D4EM standalone tool.

    'Private Sub mnuLayerDownload_Click(sender As System.Object, e As System.EventArgs) Handles mnuLayerDownload.Click, tbDownload.Click
    '    Try

    '        With New frmD4EM
    '            '.Owner = Me
    '            '.Show()
    '            .ShowDialog(Me)
    '        End With

    '    Catch ex As Exception

    '        ErrorMsg(, ex)

    '    Finally

    '        ProgressComplete()

    '    End Try

    'End Sub

    Private Sub mnuLocate_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuLocate.Click
        Try
            txtLocation.Text = InputBox("Enter the Station ID you wish to use as a new center point on the map:", "Locate", "")
            btnLocation.PerformClick()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuView_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuViewSelect.Click, mnuZoomWindow.Click, mnuZoomIn.Click, mnuZoomOut.Click, mnuZoomPrevious.Click, mnuZoomExtents.Click, mnuPan.Click, mnuCopyToClipboard.Click, mnuSelect2.Click, mnuZoomWindow2.Click, mnuZoomIn2.Click, mnuZoomOut2.Click, mnuZoomPrevious2.Click, mnuZoomExtents2.Click, mnuPan2.Click, mnuCopyClipboard2.Click, tbSelect.Click, tbZoomWindow.Click, tbZoomIn.Click, tbZoomOut.Click, tbZoomPrevious.Click, tbZoomExtents.Click, tbPan.Click, tbCopy.Click
        Try
            'tbSelect.Checked = False
            tbZoomIn.Checked = False
            tbZoomOut.Checked = False
            tbZoomWindow.Checked = False
            tbPan.Checked = False
            mnuViewSelect.Checked = False
            mnuZoomIn.Checked = False
            mnuZoomOut.Checked = False
            mnuZoomWindow.Checked = False
            mnuPan.Checked = False
            mnuSelect2.Checked = False
            mnuZoomIn2.Checked = False
            mnuZoomOut2.Checked = False
            mnuZoomWindow2.Checked = False
            mnuPan2.Checked = False

            With GISMap
                If sender Is mnuViewSelect Or sender Is mnuSelect2 Or sender Is tbSelect Then
                    .ActivateMapFunction(New MapFunctionSelectPan(GISMap))
                    'tbSelect.Checked = True
                    mnuViewSelect.Checked = True
                    mnuSelect2.Checked = True
                ElseIf sender Is mnuZoomWindow Or sender Is mnuZoomWindow2 Or sender Is tbZoomWindow Then
                    .FunctionMode = DotSpatial.Controls.FunctionMode.ZoomIn
                    tbZoomWindow.Checked = True
                    mnuZoomWindow.Checked = True
                    mnuZoomWindow2.Checked = True
                ElseIf sender Is mnuZoomIn Or sender Is mnuZoomIn2 Or sender Is tbZoomIn Then
                    .FunctionMode = DotSpatial.Controls.FunctionMode.ZoomIn
                    .ZoomIn()
                    tbZoomIn.Checked = True
                    mnuZoomIn.Checked = True
                    mnuZoomIn2.Checked = True
                ElseIf sender Is mnuZoomOut Or sender Is mnuZoomOut2 Or sender Is tbZoomOut Then
                    .ZoomOut()
                    .FunctionMode = DotSpatial.Controls.FunctionMode.ZoomOut
                    tbZoomOut.Checked = True
                    mnuZoomOut.Checked = True
                    mnuZoomOut2.Checked = True
                ElseIf sender Is mnuZoomPrevious Or sender Is mnuZoomPrevious2 Or sender Is tbZoomPrevious Then
                    .ZoomToPrevious()
                    'tbSelect.Checked = True
                    mnuViewSelect.Checked = True
                    mnuSelect2.Checked = True
                ElseIf sender Is mnuZoomExtents Or sender Is mnuZoomExtents2 Or sender Is tbZoomExtents Then
                    ZoomExtents()
                    'tbSelect.Checked = True
                    mnuViewSelect.Checked = True
                    mnuSelect2.Checked = True
                ElseIf sender Is mnuPan Or sender Is mnuPan2 Or sender Is tbPan Then
                    .FunctionMode = DotSpatial.Controls.FunctionMode.Pan
                    tbPan.Checked = True
                    mnuPan.Checked = True
                    mnuPan2.Checked = True
                ElseIf sender Is mnuCopyToClipboard Or sender Is mnuCopyClipboard2 Or sender Is tbCopy Then
                    Application.DoEvents() 'to make context menu disappear
                    CopyMap(GISMap)
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuViewAnimate_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuViewAnimate.Click
        Try
            tblData.Visible = True
            tbAnimPlay.Checked = mnuViewAnimate.Checked
            AnimTimer.Enabled = tbAnimPlay.Checked
            AnimationInfo.IsAnimating = True
            GISMap.ProgressHandler = Nothing
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuViewAttributes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuViewAttributes.Click, tbAttrTable.Click
        Try
            With New frmTable
                .Text = "WRDB GIS Attribute Table"
                If GISMap.Layers.SelectedLayer IsNot Nothing Then
                    If TypeOf GISMap.Layers.SelectedLayer Is FeatureLayer Then
                        .SetShapeFile(GISMap.Layers.SelectedLayer.DataSet)
                        .ShowDialog(Me)
                    Else
                        WarningMsg("You can only display attributes from shape file (feature) layers. Please select the layer name associated with a shape file (and not a group or category name).")
                    End If
                Else
                    WarningMsg("Please select the layer first.")
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuViewData_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuViewData.Click, tbDataTable.Click
        Try
            If AnimationInfo.dsData Is Nothing Then
                WarningMsg("You must open a data table before you can view it.")
                Exit Sub
            End If
            With New frmTable
                .SetDataSource(AnimationInfo.dsData)
                .ShowDialog(Me)
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    'Private Sub trkAnim_VisibleChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles trkAnim.VisibleChanged
    '    If trkAnim.Visible Then ResetAnimationLimits()
    'End Sub

    ''' <summary>
    ''' Clear statusbar text and progress
    ''' </summary>
    Public Sub ProgressComplete()
        Try
            If ProgressBar Is Nothing Then Exit Sub 'in case closed form during update
            Dim state As Object = Nothing
            Me.SynchContext.Send(
                Sub()
                    ProgressBar.Visible = False
                    If Not AnimationInfo.IsAnimating Then StatusLabel.Text = ""
                    statusMain.Refresh()
                    Application.UseWaitCursor = False
                    Me.Cursor = Cursors.Default 'needed because Logger calls modCommon version which sets form cursor
                    Application.DoEvents()
                End Sub, state)

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Function Statistic() As enumStatistic
        If cboStatistic.SelectedIndex = -1 Then
            Return enumStatistic.Mean 'if disabled because not aggregating
        Else
            Return cboStatistic.SelectedIndex
        End If
        'If cboStatistic.Text Like "*No.*" Then
        '    Return enumStatistic.NumResult
        'ElseIf cboStatistic.Text Like "*Minimum*" Then
        '    Return enumStatistic.MinResult
        'ElseIf cboStatistic.Text Like "*Maximum*" Then
        '    Return enumStatistic.MaxResult
        'ElseIf cboStatistic.Text Like "*Average*" Or cboStatistic.SelectedIndex = -1 Then
        '    Return enumStatistic.AvgResult
        'Else
        '    Return enumStatistic.None
        'End If
    End Function

    ''' <summary>
    ''' Reset display attributes (colors and/or sizes) of station points
    ''' </summary>
    Private Sub RefreshStationsDisplay()
        Try
            If IsCancelled Or SuspendMapUpdate Then Exit Sub

            If Not tblAnim.Enabled Then WriteProgress("Refreshing display...")

            If WRDBStationsLayer IsNot Nothing Then
                If Mode <> enumMode.Animate Then
                    If Mode = enumMode.OutputData AndAlso OutputDataOption = clsProject.enumGisOutputOption.Grid AndAlso WRDBGridLayer IsNot Nothing Then
                        GISMap.Layers.SelectedLayer = GetLayer(WRDBGridLayerName)
                    Else
                        'If Not WRDBStationsLayer.IsVisible OrElse (cboVariable.Visible And cboVariable.Text = "") Then Exit Sub
                        If (cboVariable.Visible And cboVariable.Text = "") Then Exit Sub
                        GISMap.Layers.SelectedLayer = GetLayer(WRDBStationsLayerName)
                        WRDBStationsLayer.IsVisible = True
                        If GISMap.Layers.SelectedLayer IsNot Nothing AndAlso TypeOf GISMap.Layers.SelectedLayer Is FeatureLayer AndAlso CType(GISMap.Layers.SelectedLayer, FeatureLayer).LabelLayer IsNot Nothing Then
                            mnuViewLabels.Checked = CType(GISMap.Layers.SelectedLayer, FeatureLayer).LabelLayer.IsVisible
                        End If
                    End If
                End If

            End If

            If AnimationInfo.dsData IsNot Nothing AndAlso AnimationInfo.DataLayer IsNot Nothing Then
                With GISMap
                    If mnuViewLabels.Checked Then
                        .CollisionDetection = True
                        .AddLabels(AnimationInfo.DataLayer, "", New Font("Arial", 8), Color.Black)
                    End If
                End With
            End If

            Dim WRDBPointLayer As MapPointLayer = GetLayer(WRDBStationsLayerName)

            Select Case Mode
                Case enumMode.OutputData

                    If WRDBStationsLayer IsNot Nothing And WRDBPointLayer IsNot Nothing Then

                        Dim pntScheme As DotSpatial.Symbology.PointScheme = Nothing

                        If OutputDataOption <> clsProject.enumGisOutputOption.Grid Then 'only update symbology if showing points

                            Dim minsize As Integer = 6
                            Dim maxsize As Integer = 24

                            'CreateCategories does not seem to work automatically; create my own equally spaced categories manually
                            Dim NumCat As Integer = 20
                            Dim range, minrange As Double
                            If Statistic() = enumStatistic.Num Then
                                range = AnimationInfo.MaxNum - AnimationInfo.MinNum + 0.0001
                                minrange = AnimationInfo.MinNum
                            Else
                                range = AnimationInfo.MaxValue - AnimationInfo.MinValue + 0.0001
                                minrange = AnimationInfo.MinValue
                            End If
                            'set up symbology based on value
                            pntScheme = New DotSpatial.Symbology.PointScheme
                            With pntScheme
                                .SuspendChangeEvent()
                                .SuspendEvents()
                                Dim fieldname As String = ""
                                Select Case Statistic()
                                    Case enumStatistic.Num : fieldname = "NumResult"
                                    Case enumStatistic.Min : fieldname = "MinResult"
                                    Case enumStatistic.Max : fieldname = "MaxResult"
                                    Case enumStatistic.Mean : fieldname = "AvgResult"
                                    Case Else
                                        WarningMsg("This statistic type is not supported.")
                                        Exit Sub
                                End Select
                                With .EditorSettings
                                    .NumBreaks = NumCat
                                    .ClassificationType = DotSpatial.Symbology.ClassificationType.Quantities
                                    .IntervalMethod = DotSpatial.Symbology.IntervalMethod.EqualInterval
                                    .FieldName = fieldname
                                    .IntervalSnapMethod = DotSpatial.Symbology.IntervalSnapMethod.Rounding
                                End With
                                .ClearCategories()
                                For i As Integer = 0 To NumCat
                                    Dim minval As Double = minrange + i * range / NumCat
                                    Dim maxval As Double = minval + range / NumCat
                                    Dim size As Double = minsize + i * 1.0 / (NumCat - 1) * (maxsize - minsize)
                                    Dim pc As New DotSpatial.Symbology.PointCategory(Color.Red, DotSpatial.Symbology.PointShape.Ellipse, size)
                                    pc.Symbolizer.SetOutline(Color.White, 1)
                                    Dim fmt As String = "N2"
                                    If cboStatistic.Text.Contains("Observations") Then fmt = "N0"
                                    pc.LegendText = String.Format("{0}-{1}", minval.ToString(fmt), maxval.ToString(fmt))
                                    pc.FilterExpression = String.Format("[{0}] >= {1} AND [{0}] < {2}", fieldname, minval.ToString(System.Globalization.CultureInfo.InvariantCulture), maxval.ToString("0.0000", System.Globalization.CultureInfo.InvariantCulture)) 'enum name is same as field name
                                    If i = NumCat Then
                                        pc.FilterExpression = String.Format("[{0}] IS NULL", fieldname)
                                        pc.Symbolizer.SetSize(New Size2D(1, 1))
                                        pc.Symbolizer.IsVisible = False
                                        pc.LegendText = "<Null>"
                                        pc.LegendItemVisible = False
                                    End If
                                    .AddCategory(pc)
                                Next
                                .ResumeChangeEvent()
                                .ResumeEvents()
                            End With
                        End If

                        'because the in-memory version of the datatable is getting modified, do not refresh from disk except first time

                        If WRDBStationsLayer.DataSet.DataTable Is Nothing Then WRDBStationsLayer.DataSet.FillAttributes()

                        'refresh in-memory data table with values from dtResults

                        If dtResults IsNot Nothing AndAlso dtResults.DefaultView IsNot Nothing Then

                            If AnimationInfo.ThisDate = Date.MinValue Then
                                dtResults.DefaultView.RowFilter = ""
                            Else
                                dtResults.DefaultView.RowFilter = $"MinDate >= #{AnimationInfo.ThisDate.ToString(Globalization.CultureInfo.InvariantCulture)}# And 
                                                                    MinDate < #{AnimationInfo.NextDate.ToString(Globalization.CultureInfo.InvariantCulture)}#"
                            End If
                            dtResults.DefaultView.Sort = "Station_ID"

                            For Each row As DataRow In WRDBStationsLayer.DataSet.DataTable.Rows 'loop through shapefile table
                                Dim rowNum As Integer = dtResults.DefaultView.Find(row(enumField.ID)) 'see if can find in filtered view
                                For i As Integer = 1 To dtResults.Columns.Count - 1
                                    Dim colName As String = dtResults.Columns(i).ColumnName
                                    If rowNum = -1 Then
                                        row.Item(colName) = DBNull.Value
                                    Else
                                        row.Item(colName) = dtResults.DefaultView(rowNum)(i)
                                    End If
                                Next
                            Next

                        End If

                        If OutputDataOption <> clsProject.enumGisOutputOption.Grid Then 'only update symbology if showing points
                            WRDBStationsLayer.Symbology = pntScheme
                            If WRDBStationsLayer.LabelLayer IsNot Nothing Then WRDBStationsLayer.LabelLayer.IsVisible = mnuViewLabels.Checked
                        End If

                        '====================================================
                        'refresh grid 
                        '====================================================

                        If OutputDataOption <> clsProject.enumGisOutputOption.Points Then
                            RefreshGrid()
                        Else
                            GISMap.Refresh()
                        End If


                    End If

                Case enumMode.DisplayStations, enumMode.SelectStations, enumMode.StationsList

                    If WRDBStationsLayer IsNot Nothing Then
                        'set up categories for symbology base on distinct station type; colors will be random and shape will be triangle
                        Dim pntScheme As New DotSpatial.Symbology.PointScheme
                        With pntScheme
                            .SuspendChangeEvent()
                            .SuspendEvents()
                            .ClearCategories()
                            .LegendText = "Station Type"
                            .AppearsInLegend = True
                            For i As Integer = 1 To cboStationType.Items.Count - 1
                                Dim ss As New SimpleSymbol(DefaultLayerColor(i), DotSpatial.Symbology.PointShape.Triangle, 12)
                                ss.UseOutline = True
                                ss.OutlineColor = Color.White
                                ss.OutlineWidth = 1
                                ss.Angle = 90
                                Dim ps As New DotSpatial.Symbology.PointSymbolizer(ss)
                                Dim pc As New DotSpatial.Symbology.PointCategory(ps)
                                If cboStationType.Items(i) = "<Null>" Then
                                    pc.FilterExpression = "[STAT_TYPE] Is NULL Or [STAT_TYPE]=''"
                                Else
                                    pc.FilterExpression = String.Format("[STAT_TYPE]='{0}'", cboStationType.Items(i))
                                End If
                                pc.LegendText = cboStationType.Items(i)
                                pc.LegendItemVisible = True
                                If cboStationType.SelectedIndex = 0 Or cboStationType.SelectedIndex = i Then .AddCategory(pc)
                            Next
                            .ResumeChangeEvent()
                            .ResumeEvents()
                        End With
                        'WRDBStationsLayer.DataSet.FillAttributes()
                        WRDBStationsLayer.Symbology = pntScheme
                        WRDBStationsLayer.ShowLabels = mnuViewLabels.Checked
                        RefreshLabels(IIf(cboStationType.SelectedIndex = 0, "", String.Format("[STAT_TYPE]='{0}'", cboStationType.Text)))
                    End If

                Case enumMode.Animate

                    'refill dtPolygon with data within time range

                    Dim fld As Integer = -1

                    If Not String.IsNullOrEmpty(AnimationInfo.DataLayerName) AndAlso AnimationInfo.DataLayer IsNot Nothing Then
                        If AnimationInfo.CurrentResults IsNot Nothing Then
                            fld = GetFieldIndex(AnimationInfo.DataLayer.DataSet.DataTable, AnimationInfo.DataAttribute)

                            'data table has all observations for all stations for selected pcode; set filter, find first record, and aggregate data
                            For r As Integer = 0 To AnimationInfo.DataLayer.DataSet.Features.Count - 1
                                With AnimationInfo.DataLayer.DataSet.DataTable.Rows(r)
                                    Dim StaID As String = ""
                                    Select Case AnimationInfo.DataLinkageType
                                        Case enumLinkageType.StationID, enumLinkageType.SegmentNum
                                            If fld = -1 Then Exit Sub
                                            StaID = TestNull(.Item(fld), "")
                                        Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK
                                            StaID = String.Format("{0},{1}", TestNull(.Item("I"), ""), TestNull(.Item("J"), ""))
                                    End Select
                                    If cboLayer.Text <> "" Then StaID &= "," & cboLayer.Text
                                    Dim value As Double = -1
                                    AnimationInfo.CurrentResults.TryGetValue(StaID, value)
                                    .Item("Result") = IIf(value = -1 Or value = 0.0 Or value = Single.MinValue, DBNull.Value, value.ToString("0.00000"))
                                    If WRDBStationsLayer IsNot Nothing Then
                                        If value >= 0 Then
                                            'todo: set shape fill color
                                            'GISMap.set_ShapeFillColor(hdl, r, ColorToUInt(GradientColor((value - AnimationInfo.MinValue) / (AnimationInfo.MaxValue - AnimationInfo.MinValue + 0.0001))))
                                            WRDBStationsLayer.SetVisible(WRDBStationsLayer.DataSet.Features(r), True)
                                        Else
                                            WRDBStationsLayer.SetVisible(WRDBStationsLayer.DataSet.Features(r), False)
                                        End If
                                    End If
                                    If IsCancelled Then Exit For
                                    If Not AnimationInfo.IsAnimating Then
                                        If mnuViewLabels.Checked And value >= 0 Then
                                            'todo:labeling 
                                            'Dim x, y As Single
                                            'With sf.QuickExtents(r)
                                            '    x = (.xMin + .xMax) / 2
                                            '    y = (.yMin + .yMax) / 2
                                            'End With
                                            'GISMap.AddLabel(AnimationInfo.DataHandle, value.ToString("N3"), ColorToUInt(Color.Black), x, y, MapWinGIS.tkHJustification.hjCenter)
                                        End If
                                        'WriteProgress(r, AnimationInfo.DataLayer.DataSet.Features.Count - 1, "Updating display...")
                                    End If
                                End With
                            Next
                        End If
                        'ProgressComplete()
                        StatusLabel.Text = AnimationInfo.ThisDate.ToString(MyDateTimeFormat)

                        RefreshPolygons()
                        If AnimationInfo.DataShowVectors Then RefreshVectors()

                    End If
            End Select

        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            If Not tblAnim.Enabled Then ProgressComplete()
            If Mode <> enumMode.Animate AndAlso WRDBStationsLayer IsNot Nothing AndAlso GISMap.Layers.Contains(WRDBStationsLayer) AndAlso WRDBStationsLayer.DataSet.NumRows > 0 Then
                Try
                    'WriteProgress("Saving attribute table...")
                    WRDBStationsLayer.DataSet.Save()
                    WRDBStationsLayer.IsVisible = Mode <> enumMode.OutputData Or OutputDataOption <> clsProject.enumGisOutputOption.Grid ' True
                Catch ex As Exception
                    Throw
                Finally
                    'ProgressComplete()
                End Try
            End If
        End Try
    End Sub

    Private Sub RefreshLabels(Filter As String)
        If WRDBStationsLayer IsNot Nothing AndAlso Not IsCancelled AndAlso CType(WRDBStationsLayer, MapPointLayer).DataSet.NumRows < 1000 Then
            'add station id labels (note: there is a bug in DS; this works OK for stations display, but not for thematic mapping of values)
            WriteProgress("Adding labels...")

            WRDBStationsLayer.LabelLayer = New MapLabelLayer(WRDBStationsLayer)

            With WRDBStationsLayer.LabelLayer
                With .Symbology.Categories
                    .Clear()
                    .Add(New LabelCategory With {.Expression = "[Station_ID]",
                                                 .FilterExpression = Filter,
                                                 .Name = "Station ID",
                                                 .Symbolizer = New LabelSymbolizer With {.PreventCollisions = False,
                                                                                         .OffsetY = 8,
                                                                                         .Orientation = ContentAlignment.TopCenter,
                                                                                         .FontSize = 8,
                                                                                         .FontFamily = "Tahoma"}
                                                })
                End With
                .CreateLabels()
                .IsVisible = mnuViewLabels.Checked
            End With
            ProgressComplete()
        End If
    End Sub

    ''' <summary>
    ''' Set title bar to include name of just opened or saved group file
    ''' </summary>
    Private Sub ResetTitleBar()
        If GISFileName = "" Then
            Text = "WRDB GIS " & My.Application.Info.Version.ToString(2)
        Else
            Text = IO.Path.GetFileName(GISFileName) & " - WRDB GIS " & My.Application.Info.Version.ToString(2)
        End If
    End Sub

    ''' <summary>
    ''' When trackbar changes, determine current date and next date for summary and store in AnimationInfo
    ''' </summary>
    Private Sub SetAnimationTimeStep()
        Try
            If Aggregate = enumAggregate.POR Then Exit Sub
            With AnimationInfo
                If .MinDate.Year = 9999 Or trkAnim.Value < 0 Then Exit Sub
                If Aggregate = enumAggregate.None AndAlso .dsData IsNot Nothing Then
                    .ThisDate = .dsData.Dates(trkAnim.Value)
                    If trkAnim.Value < trkAnim.Maximum Then
                        .NextDate = .dsData.Dates(trkAnim.Value + 1)
                    Else 'just set to a larger value
                        .NextDate = .ThisDate.AddMinutes(999)
                    End If
                Else
                    Select Case .TimeUnits
                        Case enumTimeUnits.Minute
                            .ThisDate = .MinDate.AddMinutes(trkAnim.Value)
                            .NextDate = .ThisDate.AddMinutes(.TimeStep)
                        Case enumTimeUnits.Hour
                            .ThisDate = .MinDate.AddHours(trkAnim.Value)
                            .NextDate = .ThisDate.AddHours(.TimeStep)
                        Case enumTimeUnits.Day
                            .ThisDate = .MinDate.AddDays(trkAnim.Value)
                            .NextDate = .ThisDate.AddDays(.TimeStep)
                        Case enumTimeUnits.Month
                            .ThisDate = .MinDate.AddDays(trkAnim.Value * 31) 'get it close then round to nearest beginning of the month
                            .ThisDate = New Date(.ThisDate.Year, .ThisDate.Month, 1)
                            .NextDate = .ThisDate.AddDays(DateTime.DaysInMonth(.ThisDate.Year, .ThisDate.Month))
                        Case enumTimeUnits.Year
                            .ThisDate = New Date(.MinDate.Year + trkAnim.Value, 1, 1)
                            .NextDate = .ThisDate.AddDays(IIf(DateTime.IsLeapYear(.ThisDate.Year), 366, 365))
                    End Select
                End If
                trkAnim.Minimum = 0
                ToolTip.SetToolTip(trkAnim, .ThisDate.ToString(MyDateTimeFormat))
                StatusLabel.Text = .ThisDate.ToString(MyDateTimeFormat)
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Reset animation settings including timestep (depending on aggregation), min/max dates to be animated, min/max values over all times and stations for selected pcode, 
    ''' trackbar settings, and finally, redraw the animation legend; will also call ResetTrackbar
    ''' This is called whenever a file is loaded or the pcode, aggregation, or display type is changed
    ''' </summary>
    Private Sub ResetAnimation()
        Try
            If SuspendMapUpdate Or IsCancelled Then Exit Sub

            With AnimationInfo

                If .dsData Is Nothing Then Exit Sub

                Select Case Aggregate
                    Case enumAggregate.POR 'period of record; don't group by time but summarize for all data
                        .TimeUnits = enumTimeUnits.Day
                        .TimeStep = 1
                    Case enumAggregate.None
                        .TimeUnits = enumTimeUnits.Hour
                        .TimeStep = 1
                    Case enumAggregate.Day
                        .TimeUnits = enumTimeUnits.Day
                        .TimeStep = 1
                    Case enumAggregate.Month
                        .TimeUnits = enumTimeUnits.Month
                        .TimeStep = 1
                    Case enumAggregate.Growing 'need to recompute because SQL can't determine if in growing season
                        .TimeUnits = enumTimeUnits.Year
                        .TimeStep = 1
                    Case enumAggregate.Annual
                        .TimeUnits = enumTimeUnits.Year
                        .TimeStep = 1
                End Select
            End With

            'find min and max value for selected pcode for all station ids (only if station ID is in shape file) so can determine limits for trackbar and color range

            'Dim staList As String = ""
            'For Each StaID As String In AnimationInfo.StationList
            '    staList &= IIf(staList = "", "", ",") & "'" & StaID & "'"
            'Next

            Dim MinDate As Date = Date.MaxValue
            Dim MaxDate As Date = Date.MinValue
            Dim MinValue As Double = Double.MaxValue, MaxValue As Double = Double.MinValue

            With AnimationInfo

                MinDate = .dsData.MinDate
                MaxDate = .dsData.MaxDate

                Select Case Aggregate
                    Case enumAggregate.Annual, enumAggregate.Growing
                        .MinDate = New Date(MinDate.Year, 1, 1)
                        .MaxDate = New Date(MaxDate.Year + 1, 1, 1)
                    Case enumAggregate.Day
                        .MinDate = MinDate.Date
                        .MaxDate = MaxDate.Date
                    Case Else
                        .MinDate = MinDate
                        .MaxDate = MaxDate
                End Select
                trkAnim.SmallChange = .TimeStep
                trkAnim.LargeChange = .TimeStep
                trkAnim.TickFrequency = .TimeStep
                trkAnim.Minimum = 0

                If Aggregate = enumAggregate.None Then 'only true for BMD files
                    trkAnim.Maximum = .dsData.Dates.Count - 1
                Else
                    Select Case .TimeUnits
                        Case enumTimeUnits.Minute : trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalMinutes
                        Case enumTimeUnits.Hour : trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalHours
                        Case enumTimeUnits.Day : trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalDays
                        Case enumTimeUnits.Month : trkAnim.Maximum = (.MaxDate.Year - .MinDate.Year) * 12 + (.MaxDate.Month - .MinDate.Month)
                        Case enumTimeUnits.Year : trkAnim.Maximum = .MaxDate.Year - .MinDate.Year - 1
                    End Select
                End If
                trkAnim.Value = trkAnim.Minimum
                SetAnimationTimeStep()
                'ResetTrackbar()

                If cboVariable.Text <> "" And cboVariable.Text <> "None" Then

                    'determine range of values for all stations and aggregated timesteps so can properly assign colors

                    'determine range of values for all stations and dates
                    Dim dictMinResults, dictMaxResults As Dictionary(Of String, Single)

                    If cboVariable.Text <> "None" Then
                        If Statistic() = enumStatistic.Num Then
                            dictMaxResults = .dsData.GetResults(.StationList, CType(.DataLinkageType, WRDB.DataSource.clsData.enumLinkageType), cboVariable.Text, .MinDate, .MaxDate, DataSource.clsData.enumAggregateFunction.Num)
                            For Each Staid As String In .StationList
                                'ignore -1 values representing missing values
                                MinValue = Math.Min(MinValue, dictMaxResults(Staid))
                                MaxValue = Math.Max(MaxValue, dictMaxResults(Staid))
                            Next
                            '.MinValue = 0
                            .MinValue = MinValue
                            .MaxValue = MaxValue
                        Else
                            If .ScalingOption = enumScalingOption.DataRange Or Aggregate = enumAggregate.None Then 'original method with range is always based on range of data

                                'note: on 3/8/13 we found bug in EPDRiv1 BMD structure where varsegminmax stored; temporarily changed clsDataBMD to use slower method to determine min/max
                                dictMinResults = .dsData.GetResults(.StationList, CType(.DataLinkageType, WRDB.DataSource.clsData.enumLinkageType), cboVariable.Text, .MinDate, .MaxDate, DataSource.clsData.enumAggregateFunction.Min)
                                dictMaxResults = .dsData.GetResults(.StationList, CType(.DataLinkageType, WRDB.DataSource.clsData.enumLinkageType), cboVariable.Text, .MinDate, .MaxDate, DataSource.clsData.enumAggregateFunction.Max)
                                For Each Staid As String In .StationList
                                    'ignore -1 values representing missing values (and also 0.000 values found in EFDC BMD files)
                                    If dictMinResults(Staid) <> 0 And dictMinResults(Staid) <> -1 Then MinValue = Math.Min(MinValue, dictMinResults(Staid))
                                    If dictMaxResults(Staid) <> 0 And dictMaxResults(Staid) <> -1 Then MaxValue = Math.Max(MaxValue, dictMaxResults(Staid))
                                Next
                                If MinValue = Double.MaxValue Then MinValue = 0.0 'didn't encounter any values other than -1 or 0.0
                                If MaxValue = Double.MinValue Then MaxValue = 0.0 'didn't encounter any values other than -1 or 0.0

                            Else 'determine range for selected aggregation and statistic

                                'for each timestep get range of values at all cells
                                Dim dt1 As Date = MinDate
                                Dim dt2 As Date = MinDate
                                While dt1 < MaxDate
                                    If Aggregate = enumAggregate.POR Then
                                        dt2 = Date.MaxValue
                                    Else
                                        Select Case .TimeUnits
                                            Case enumTimeUnits.Minute : dt2 = dt1.AddMinutes(1)
                                            Case enumTimeUnits.Hour : dt2 = dt1.AddHours(1)
                                            Case enumTimeUnits.Day : dt2 = dt1.AddDays(1)
                                            Case enumTimeUnits.Month : dt2 = dt1.AddDays(DateTime.DaysInMonth(dt1.Year, dt1.Month))
                                            Case enumTimeUnits.Year : dt2 = dt1.AddDays(IIf(DateTime.IsLeapYear(dt1.Year), 366, 365))
                                        End Select
                                    End If
                                    Dim dictResults = .dsData.GetResults(.StationList, CType(.DataLinkageType, WRDB.DataSource.clsData.enumLinkageType), cboVariable.Text, dt1, dt2, CType(cboStatistic.SelectedIndex, DataSource.clsData.enumAggregateFunction))
                                    For Each Staid As String In .StationList
                                        'ignore -1 values representing missing values (and also 0.000 values found in EFDC BMD files)
                                        If dictResults(Staid) <> 0 And dictResults(Staid) <> -1 Then
                                            MinValue = Math.Min(MinValue, dictResults(Staid))
                                            MaxValue = Math.Max(MaxValue, dictResults(Staid))
                                        End If
                                    Next
                                    dt1 = dt2
                                End While
                                If MinValue = Double.MaxValue Then MinValue = 0.0 'didn't encounter any values other than -1 or 0.0
                                If MaxValue = Double.MinValue Then MaxValue = 0.0 'didn't encounter any values other than -1 or 0.0

                            End If

                            .MinValue = MinValue
                            .MaxValue = MaxValue

                        End If
                    End If
                End If

                If .DataShowVectors Then
                    'determine range of vector magnitudes
                    .MinVector = 0
                    .MaxVector = Double.MinValue
                    Dim agg As DataSource.clsData.enumAggregateFunction = DataSource.clsData.enumAggregateFunction.Max
                    If Statistic() = enumStatistic.Num Then agg = DataSource.clsData.enumAggregateFunction.Num
                    For Each Sta In .dsData.GetResults(.StationList, .DataXParm, .DataYParm, .MinDate, .MaxDate, agg)
                        .MaxVector = Math.Max(.MaxVector, Sta.Value.Item1)
                    Next
                End If

                If Aggregate = enumAggregate.POR Then
                    .ThisDate = .MinDate
                    .NextDate = .MaxDate
                End If

                ResetTrackbar()

            End With
            DrawLegend()
            RefreshPolygons()
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            'ProgressComplete()
        End Try
    End Sub

    ''' <summary>
    ''' Only call this when displaying data by pcode
    ''' </summary>
    ''' <remarks>When first create Stations shape file, this is called first to set up dtResults; thereafter when PCode changes, this is called again to update the fields in the attribute table</remarks>
    Friend Sub SummarizeStationData()
        Try
            With WRDBProject
                'If MainForm IsNot Nothing Then MainForm.WriteProgress("Summarizing data...", ProgressBarStyle.Marquee)
                'WriteProgress("Summarizing data...")
                'query all stations in support table (or referenced in active working/master or temp table) 
                Select Case Mode
                    Case enumMode.OutputData
                        Dim sqlGroup As String = ""
                        Dim sqlWhere As String = ""
                        With AnimationInfo
                            .TimeUnits = enumTimeUnits.Day
                            .TimeStep = 1
                        End With
                        tblAnim.Enabled = True

                        Select Case Aggregate
                            Case enumAggregate.POR 'period of record; don't group by time but summarize for all data
                                tblAnim.Enabled = False
                                With AnimationInfo
                                    .TimeUnits = enumTimeUnits.None
                                    .ThisDate = Date.MinValue '.MinDate
                                    .NextDate = Date.MaxValue '.MaxDate
                                End With

                            Case enumAggregate.None
                                sqlGroup = ", Date_Time"
                                With AnimationInfo
                                    .TimeUnits = enumTimeUnits.Hour
                                End With

                            Case enumAggregate.Day
                                Select Case .DB.DatabaseType
                                    Case clsDB.enumDBType.Access, clsDB.enumDBType.Paradox, clsDB.enumDBType.DBase, clsDB.enumDBType.Excel, clsDB.enumDBType.Text
                                        sqlGroup = ", DATE(Date_Time)"
                                    Case clsDB.enumDBType.SQL_Server, clsDB.enumDBType.SQL_Server_CE
                                        sqlGroup = ", CONVERT(DATE,Date_Time)"
                                    Case clsDB.enumDBType.SQLite
                                        sqlGroup = ", DATE(Date_Time)"
                                    Case Else
                                        sqlGroup = ", EXTRACT(DATE from Date_Time)"
                                End Select
                                AnimationInfo.TimeUnits = enumTimeUnits.Day

                            Case enumAggregate.Month
                                Select Case .DB.DatabaseType
                                    Case clsDB.enumDBType.Access, clsDB.enumDBType.Paradox, clsDB.enumDBType.DBase, clsDB.enumDBType.Excel, clsDB.enumDBType.Text, clsDB.enumDBType.SQL_Server, clsDB.enumDBType.SQL_Server_CE
                                        sqlGroup = ", MONTH(Date_Time), YEAR(Date_Time)"
                                    Case clsDB.enumDBType.SQLite
                                        sqlGroup = ", STRFTIME('%m',Date_Time), STRFTIME('%Y',Date_Time)"
                                    Case Else
                                        sqlGroup = ", EXTRACT(MONTH from Date_Time), EXTRACT(YEAR from Date_Time)"
                                End Select
                                AnimationInfo.TimeUnits = enumTimeUnits.Month

                            Case enumAggregate.Growing
                                Select Case .DB.DatabaseType
                                    Case clsDB.enumDBType.Access, clsDB.enumDBType.Paradox, clsDB.enumDBType.DBase, clsDB.enumDBType.Excel, clsDB.enumDBType.Text, clsDB.enumDBType.SQL_Server, clsDB.enumDBType.SQL_Server_CE
                                        sqlGroup = ", YEAR(Date_Time)"
                                        sqlWhere = String.Format(" AND MONTH(Date_Time) * 100 + DAY(Date_Time) >= {0} AND MONTH(Date_Time) * 100 + DAY(Date_Time) <= {1}", GrowingMonth1 * 100 + GrowingDay1, GrowingMonth2 * 100 + GrowingDay2)
                                    Case clsDB.enumDBType.SQLite
                                        sqlGroup = ", STRFTIME('%Y',Date_Time)"
                                        sqlWhere = String.Format(" AND STRFTIME('%m',Date_Time) * 100 + STRFTIME('%D',Date_Time) >= {0} AND STRFTIME('%m',Date_Time) * 100 + STRFTIME('%D',Date_Time) <= {1}", GrowingMonth1 * 100 + GrowingDay1, GrowingMonth2 * 100 + GrowingDay2)
                                    Case Else
                                        sqlGroup = ", EXTRACT(YEAR from Date_Time) as Year"
                                        sqlWhere = String.Format(" AND EXTRACT(MONTH FROM Date_Time) * 100 + EXTRACT(DAY FROM Date_Time) >= {0} AND EXTRACT(MONTH FROM Date_Time) * 100 + EXTRACT(DAY FROM Date_Time) <= {1}", GrowingMonth1 * 100 + GrowingDay1, GrowingMonth2 * 100 + GrowingDay2)
                                End Select
                                AnimationInfo.TimeUnits = enumTimeUnits.Year

                            Case enumAggregate.Annual
                                Select Case .DB.DatabaseType
                                    Case clsDB.enumDBType.Access, clsDB.enumDBType.Paradox, clsDB.enumDBType.DBase, clsDB.enumDBType.Excel, clsDB.enumDBType.Text, clsDB.enumDBType.SQL_Server, clsDB.enumDBType.SQL_Server_CE
                                        sqlGroup = ", YEAR(Date_Time)"
                                    Case clsDB.enumDBType.SQLite
                                        sqlGroup = ", STRFTIME('%Y',Date_Time)"
                                    Case Else
                                        sqlGroup = ", EXTRACT(YEAR from Date_Time)"
                                End Select
                                AnimationInfo.TimeUnits = enumTimeUnits.Year

                        End Select

                        Dim Sql As String =
$"SELECT Dta.Station_ID as Station_ID, 
COUNT(Result) As NumResult, 
MIN(Result) as MinResult, 
MAX(Result) as MaxResult, 
ROUND(AVG(Result),8) as AvgResult, 
MIN(Date_Time) as MinDate, 
MAX(Date_Time) as MaxDate 
FROM {TempData} Dta 
LEFT OUTER JOIN {WRDBProject.FullTableName(clsProject.enumTableTypes.Stations)} Stations 
     ON Dta.Station_ID=Stations.Station_ID 
WHERE Dta.Station_ID Is Not NULL And 
     PCode='{cboVariable.Text}' AND 
     Selected_By='{clsApplicationSettings.UserName}' AND
     Date_Time IS NOT NULL {sqlWhere} AND 
     Result Is Not NULL And 
     Date_Time>{ .DB.DateTime(New Date(1900, 1, 1))} And 
     Latitude Is Not NULL And Latitude <> 0.0 And 
     Longitude Is Not NULL And Longitude <> 0.0 
GROUP BY Dta.Station_ID{sqlGroup}"

                        'need to disable to dont get open data reader error
                        tbAnimNext.Enabled = False
                        tbAnimPrev.Enabled = False
                        tbAnimBeg.Enabled = False
                        tbAnimEnd.Enabled = False
                        trkAnim.Enabled = False

                        If Not .DB.AddTable(dtResults, Sql) Then Exit Sub

                        tbAnimNext.Enabled = True
                        tbAnimPrev.Enabled = True
                        tbAnimBeg.Enabled = True
                        tbAnimEnd.Enabled = True
                        trkAnim.Enabled = True

                        dtResults.PrimaryKey = New DataColumn() {dtResults.Columns("Station_ID"), dtResults.Columns("MinDate")}

                        If WRDBStationsLayer IsNot Nothing AndAlso GISMap.Layers.Contains(WRDBStationsLayer) Then
                            'refresh previously created shape file with updated data
                            WRDBStationsLayer.DataSet.DataTable.PrimaryKey = New DataColumn() {WRDBStationsLayer.DataSet.DataTable.Columns("Station_ID")}
                            For Each row As DataRow In dtResults.Rows
                                Dim rowStations As DataRow = WRDBStationsLayer.DataSet.DataTable.Rows.Find(row.Item("Station_ID"))
                                If rowStations IsNot Nothing Then
                                    For i As Integer = 1 To dtResults.Columns.Count - 1
                                        Dim colName As String = dtResults.Columns(i).ColumnName
                                        rowStations.Item(colName) = row.Item(colName)
                                    Next
                                End If
                            Next
                            WRDBStationsLayer.DataSet.Save()
                        End If

                        'Dim staList As String = ""

                        Dim MinNum, MaxNum As Integer
                        Dim MinValue, MaxValue As Double
                        Dim MinDate, MaxDate As Date

                        'sqlite puts all but first column in datatable as objects, so .Compute fails

                        With dtResults.DefaultView
                            'Dim rowfilter As String = ""
                            'If staList <> "" Then rowfilter = String.Format("Station_ID IN ({0}) AND Result IS NOT NULL", staList)
                            'If rowfilter.Length < 5000 Then .RowFilter = rowfilter
                            If .Count > 0 Then
                                .Sort = "NumResult"
                                MinNum = .Item(0).Item("NumResult")
                                MaxNum = .Item(.Count - 1).Item("NumResult")
                                .Sort = "MinDate"
                                MinDate = .Item(0)("MinDate")
                                .Sort = "MaxDate"
                                MaxDate = .Item(.Count - 1)("MaxDate")
                                .Sort = "MinResult"
                                MinValue = .Item(0)("MinResult")
                                .Sort = "MaxResult"
                                MaxValue = .Item(.Count - 1)("MaxResult")
                            End If
                        End With

                        With AnimationInfo
                            Select Case Aggregate
                                Case enumAggregate.Annual, enumAggregate.Growing
                                    .MinDate = New Date(MinDate.Year, 1, 1)
                                    .MaxDate = New Date(MaxDate.Year + 1, 1, 1)
                                Case enumAggregate.Day
                                    .MinDate = MinDate.Date
                                    .MaxDate = MaxDate.Date
                                Case enumAggregate.Month
                                    .MinDate = New Date(MinDate.Year, MinDate.Month, 1)
                                    .MaxDate = New Date(MaxDate.Year, MaxDate.Month, 1)
                                Case Else
                                    .MinDate = MinDate
                                    .MaxDate = MaxDate
                            End Select

                            .MinValue = MinValue
                            .MaxValue = MaxValue
                            .MinNum = MinNum
                            .MaxNum = MaxNum

                            trkAnim.SmallChange = .TimeStep
                            trkAnim.LargeChange = .TimeStep
                            trkAnim.Minimum = 0
                            Select Case .TimeUnits
                                Case enumTimeUnits.Minute
                                    trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalMinutes
                                Case enumTimeUnits.Hour
                                    trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalHours
                                Case enumTimeUnits.Day
                                    trkAnim.Maximum = .MaxDate.Subtract(.MinDate).TotalDays
                                Case enumTimeUnits.Month
                                    'trkAnim.Maximum = (.MaxDate.Year - .MinDate.Year) * 12 - 1
                                    trkAnim.Maximum = CInt(New Date(.MaxDate.Year, .MaxDate.Month, 1).Subtract(New Date(.MinDate.Year, .MinDate.Month, 1)).TotalDays / 30)
                                Case enumTimeUnits.Year
                                    trkAnim.Maximum = .MaxDate.Year - .MinDate.Year - 1
                                Case enumTimeUnits.None
                                    trkAnim.Maximum = 0
                            End Select
                            'trkAnim.Value = 0
                            'If .ThisDate = Date.MinValue Then trkAnim.Value = 0
                            SetAnimationTimeStep()
                        End With
                End Select
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            'ProgressComplete()
        End Try
    End Sub

    Private Sub tbAnim_ItemClicked(ByVal sender As System.Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles tbAnim.ItemClicked
        Try
            With trkAnim
                If e.ClickedItem Is tbAnimBeg Then
                    tbAnimPause.PerformClick()
                    .Value = .Minimum
                ElseIf e.ClickedItem Is tbAnimEnd Then
                    tbAnimPause.PerformClick()
                    .Value = .Maximum
                ElseIf e.ClickedItem Is tbAnimNext Then
                    tbAnimPause.PerformClick()
                    If .Value + .SmallChange > .Maximum Then
                        .Value = .Minimum
                    Else
                        .Value += .SmallChange
                    End If
                ElseIf e.ClickedItem Is tbAnimPrev Then
                    tbAnimPause.PerformClick()
                    If .Value - .SmallChange < .Minimum Then
                        .Value = .Maximum
                    Else
                        .Value -= .SmallChange
                    End If
                ElseIf e.ClickedItem Is tbAnimPause Then
                    AnimTimer.Enabled = False
                    IsCancelled = True
                    tbAnimPlay.Checked = False
                    mnuViewAnimate.Checked = False
                    AnimationInfo.IsAnimating = False
                ElseIf e.ClickedItem Is tbAnimPlay Then
                    AnimTimer.Enabled = Not tbAnimPlay.Checked
                    AnimationInfo.IsAnimating = Not tbAnimPlay.Checked
                    IsCancelled = Not AnimTimer.Enabled
                    mnuViewAnimate.Checked = Not tbAnimPlay.Checked
                ElseIf e.ClickedItem Is tbAnimClose Then
                    tbAnimPause.PerformClick()
                    tblData.Visible = False
                End If
            End With
            'If AnimationInfo.IsAnimating Then
            '    'note: the following does not work properly (the messages continue to get sent); I will have to disable in clsMapProgressHandler routine.
            '    GISMap.ProgressHandler = Nothing 'want to turn off messages
            'Else
            '    GISMap.ProgressHandler = New clsMapProgressHandler
            'End If
            ToolTip.Active = Not AnimTimer.Enabled
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub tbAnimSpeed_DropDownItemClicked(ByVal sender As Object, ByVal e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles tbAnimSpeed.DropDownItemClicked, mnuViewAnimateSpeed.DropDownItemClicked
        Dim Index As Integer = sender.DropDownItems.IndexOf(e.ClickedItem)
        AnimationInfo.Speed = Index + 1
        If Index >= 4 Then 'turbo mode loops many times when timer ticks
            AnimTimer.Interval = 1
        Else
            AnimTimer.Interval = 1 + (4 - Index) * 100 'will range from 1 to 401 milliseconds delay
        End If
        CheckDropDownItem(mnuViewAnimateSpeed, Index)
        CheckDropDownItem(tbAnimSpeed, Index)
    End Sub

    Private Sub trkAnim_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles trkAnim.MouseDown
        tbAnimPause.PerformClick()
    End Sub

    Private Sub trkAnim_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles trkAnim.ValueChanged
        IsCancelled = False
        ResetTrackbar()
        For Each f As WRDB.Graph.frmGraph In GraphForms()
            f.SetAnimationTrackbar(trkAnim.Value)
        Next
    End Sub

    ''' <summary>
    ''' If animating, summarize station data, reset timestep, and refresh display
    ''' </summary>
    Private Sub ResetTrackbar()
        Try
            If SuspendMapUpdate Or Not ModeSettings.AllowAnimation Then Exit Sub
            SetAnimationTimeStep()
            If Mode = enumMode.OutputData Then
                SummarizeStationData()
            ElseIf Mode = enumMode.Animate Then
                With AnimationInfo
                    If .dsData IsNot Nothing Then
                        Dim aggfunc As DataSource.clsData.enumAggregateFunction = Statistic() 'these two enums have the same values
                        If cboVariable.Text <> "None" Then .CurrentResults = .dsData.GetResults(.StationList, CType(.DataLinkageType, WRDB.DataSource.clsData.enumLinkageType), cboVariable.Text, .ThisDate, .NextDate, aggfunc)
                        'don't use aggregation statistic for vectors; always return mean velocity in aggregation period
                        'If .DataShowVectors Then .CurrentVectors = .dsData.GetResults(.StationList, .DataXParm, .DataYParm, .ThisDate, .NextDate, aggfunc)
                        If .DataShowVectors Then .CurrentVectors = .dsData.GetResults(.StationList, .DataXParm, .DataYParm, .ThisDate, .NextDate, DataSource.clsData.enumAggregateFunction.Mean)
                    End If
                End With
            End If
            RefreshStationsDisplay()
            ToolTip.Active = Not AnimationInfo.IsAnimating
        Catch ex As Exception
            AnimTimer.Stop()
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Show text on statusbar (causes progressbar to be marquee)
    ''' </summary>
    ''' <param name="FormatString">Text to display on statuslabel (may include formatting text for String.Format)</param>
    ''' <param name="Args">Optional arguments to pass to FormatString</param>
    Public Sub WriteProgress(FormatString As String, ParamArray Args() As Object)
        Try
            Dim state As Object = New Object() {FormatString, Args}
            Me.SynchContext.Send(
                Sub(ArgObj As Object)
                    ProgressBar.Visible = True
                    ProgressBar.Style = ProgressBarStyle.Marquee
                    StatusLabel.Text = String.Format(FormatString, Args)
                    statusMain.Refresh()
                    Application.UseWaitCursor = True
                    Application.DoEvents()
                End Sub, state)
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Show text and task progress on statusbar 
    ''' </summary>
    ''' <param name="Value">Current value for progressbar</param>
    ''' <param name="MaxValue">Maximum value for progressbar</param>
    ''' <param name="FormatString">Text to display on statuslabel (may include formatting text for String.Format)</param>
    ''' <param name="Args">Optional arguments to pass to FormatString</param>
    Public Sub WriteProgress(ByVal Value As Integer, ByVal MaxValue As Integer, ByVal FormatString As String, ByVal ParamArray Args() As Object)
        Try
            Static LastTimer As Integer = My.Computer.Clock.TickCount
            Dim state As Object = New Object() {FormatString, Args}
            Me.SynchContext.Send(
                Sub(ArgObj As Object)
                    If My.Computer.Clock.TickCount - LastTimer < 500 Then Exit Sub 'don't let it update too frequently!
                    With ProgressBar
                        .Style = ProgressBarStyle.Blocks
                        .Maximum = MaxValue
                        .Value = Value
                        .Visible = True
                    End With
                    StatusLabel.Text = String.Format(FormatString, Args)
                    statusMain.Refresh()
                    Application.UseWaitCursor = True
                    Application.DoEvents()
                    LastTimer = My.Computer.Clock.TickCount
                End Sub, state)
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ZoomExtents()
        With GISMap
            .SuspendLayout()
            If .Layers.SelectedLayer Is Nothing Then
                .ViewExtents = .Extent
            Else
                If .Layers.SelectedLayer.Extent.Width > 0 And .Layers.SelectedLayer.Extent.Height > 0 Then .ViewExtents = .Layers.SelectedLayer.Extent
            End If
            With GISMap.ViewExtents 'when load points layer, some are off the screen; provide a 10% padding
                .ExpandBy((.MaxX - .MinX) * 0.05)
            End With
            .ResumeLayout(True)
            .Refresh()
        End With
    End Sub

    Private Sub mnuFileExport_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuFileExport.Click, tbExport.Click
        Try
            If Mode <> enumMode.OutputData And Mode <> enumMode.Animate Then
                WarningMsg("The export feature is only designed for use when displaying thematic output maps.")
                Exit Sub
            End If
            frmInfoMsg.ShowInfoMsg("Export Data", "This will allow you to export data for the currently thematic map for the currently selected PCode, type of data, aggregation period, and time step to a separate export file which can be used in an external GIS application. The export file can be in either of two formats:\n\n\t\b Shape file: contains all polygon or point shapes and the attribute table will have the current data file values appended. The GIS application can use the data fields to prepare a thematic map (the current thematic mapping scheme cannot be preserved).\n\n\t\b dBase table: contains only the shape keys and data values. The GIS application can 'join' this table to the original shape file so that the data field can be used to display a thematic map.")
            Dim db As New clsDBDBase
            Dim ThisDateLabel As String = Choose(Statistic() + 1, "ALL", "NUM", "MIN", "MAX", "AVG") & "_" & cboVariable.Text.Split(" ")(0)
            If Aggregate = enumAggregate.POR Then
                ThisDateLabel &= "_POR"
            Else
                ThisDateLabel &= String.Format("_{0:yyyyMMdd}", AnimationInfo.ThisDate)
            End If
            Dim ExportFile As String = ""
            With New SaveFileDialog
                .AddExtension = True
                .CheckPathExists = True
                .CreatePrompt = False
                '.DefaultExt = ".DBF"
                .Filter = "Shape File (*.shp)|*.shp|dBase Table (*.dbf)|*.dbf"
                .FilterIndex = 0
                .OverwritePrompt = True
                .Title = "Export Data File"
                .ValidateNames = True
                .InitialDirectory = GetSetting(REGAPP, Me.Name, "AddLayer", My.Computer.FileSystem.SpecialDirectories.MyDocuments & "WRDB")
                .FileName = String.Format("WRDBGIS_{0}", ThisDateLabel)
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then ExportFile = .FileName
                .Dispose()
            End With

            If ExportFile = "" Then Exit Sub

            If IO.Path.GetExtension(ExportFile).ToUpper = ".SHP" Then
                'create new shape file with all shapes in source file plus data field
                Dim ShapeFileName As String = ""
                Select Case Mode
                    Case enumMode.OutputData
                        ShapeFileName = WRDBStationsFileName
                    Case enumMode.Animate
                        ShapeFileName = AnimationInfo.DataLayer.DataSet.Filename
                End Select

                Select Case Mode
                    Case enumMode.Animate
                        AnimationInfo.DataLayer.DataSet.SaveAs(ExportFile, True)
                    Case enumMode.OutputData
                        WRDBStationsLayer.DataSet.SaveAs(ExportFile, True)
                End Select

            Else
                'write stand-alone .dbf file that can be joined with original shape file
                'field names 10 char max, start with character, 128 fields max
                'get field names for each time step

                With db

                    .DatabaseType = clsDB.enumDBType.DBase
                    .DataSource = IO.Path.GetDirectoryName(ExportFile)
                    Dim TableName As String = IO.Path.GetFileNameWithoutExtension(ExportFile)
                    .CreateConnection()

                    Dim FieldNames As New Generic.List(Of String)
                    Dim FieldTypes As New Generic.List(Of clsDB.enumColType)

                    FieldNames.Add("Station_ID")
                    FieldTypes.Add(clsDB.enumColType.String)

                    FieldNames.Add(.GetValidObjectName(ThisDateLabel))
                    FieldTypes.Add(clsDB.enumColType.Double)

                    'if shapefile already exists, will be corrupted if dbf file has same name; delete shape file

                    DeleteShapeFiles(ExportFile)

                    If .TableExists(TableName) Then .DropTable(TableName)
                    If .CreateTable(TableName, FieldNames.ToArray, FieldTypes.ToArray) Then
                        Dim ds As New Data.DataSet
                        Dim da As DbDataAdapter = .AddTable(ds, TableName)
                        If da IsNot Nothing Then
                            Dim dt As DataTable = ds.Tables(0)
                            Select Case Mode
                                Case enumMode.OutputData
                                    If dtResults IsNot Nothing Then
                                        dtResults.DefaultView.RowFilter = $"MinDate >= #{AnimationInfo.ThisDate.ToShortDateString}# AND MinDate < #{AnimationInfo.NextDate.ToShortDateString}#"
                                        dtResults.DefaultView.Sort = "Station_ID"
                                        Dim FieldName As String = .GetValidObjectName(ThisDateLabel)
                                        For r As Integer = 0 To WRDBStationsLayer.DataSet.Features.Count - 1
                                            Dim row As Integer = dtResults.DefaultView.Find(WRDBStationsLayer.DataSet.DataTable.Rows(r)(0))
                                            Dim StaID As String = ""
                                            Dim value As Single = 0
                                            Dim resultfield As String = ""
                                            Select Case Statistic()
                                                Case enumStatistic.Num : resultfield = "NumResult"
                                                Case enumStatistic.Min : resultfield = "MinResult"
                                                Case enumStatistic.Max : resultfield = "MaxResult"
                                                Case enumStatistic.Mean : resultfield = "AvgResult"
                                                Case Else
                                                    WarningMsg("This statistic type is not supported.")
                                                    Exit Sub
                                            End Select
                                            If row <> -1 Then
                                                StaID = TestNull(dtResults.DefaultView.Item(row).Item("Station_ID"), "")
                                                value = TestNull(dtResults.DefaultView.Item(row).Item(resultfield), 0)
                                            End If
                                            If StaID <> "" And value <> 0 Then
                                                Dim dr As DataRow = dt.NewRow
                                                dr.Item("Station_ID") = StaID
                                                dr.Item(FieldName) = value
                                                dt.Rows.Add(dr)
                                            End If
                                            WriteProgress(r, WRDBStationsLayer.DataSet.Features.Count - 1, "Saving data...")
                                        Next
                                    End If
                                Case enumMode.SelectStations, enumMode.DisplayStations
                                Case enumMode.Animate
                                    Dim fld As Integer = GetFieldIndex(AnimationInfo.DataLayer.DataSet.DataTable, AnimationInfo.DataAttribute)
                                    Dim FieldName As String = String.Format("{0}", .GetValidObjectName(ThisDateLabel))
                                    For r As Integer = 0 To AnimationInfo.DataLayer.DataSet.Features.Count - 1
                                        Dim StaID As String = AnimationInfo.DataLayer.DataSet.DataTable.Rows(r)(fld)
                                        Dim dr As DataRow = dt.NewRow
                                        dr.Item("Station_ID") = StaID
                                        Dim Value As Double = AnimationInfo.CurrentResults(StaID)
                                        If Value >= 0 Then
                                            dr.Item(FieldName) = Value
                                            dt.Rows.Add(dr)
                                        End If
                                        WriteProgress(r, AnimationInfo.DataLayer.DataSet.Features.Count - 1, "Saving data...")
                                    Next
                            End Select
                            WriteProgress("Saving data...")
                            Dim NumUpdated As Integer = da.Update(ds, TableName)
                            ds.Dispose()
                            da.Dispose()
                        End If
                    End If
                    .Dispose()
                End With
            End If

        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            ProgressComplete()
        End Try
    End Sub

    ''' <summary>
    ''' Reset scheme for polygon layer based on current values
    ''' </summary>
    ''' <remarks>Note: the number of categories is determined using method in DrawLegend based on height of screen</remarks>
    Friend Sub RefreshPolygons()
        Try
            With AnimationInfo

                Dim polyScheme As New DotSpatial.Symbology.PolygonScheme
                polyScheme.Categories.Clear()
                polyScheme.SuspendChangeEvent()
                polyScheme.SuspendEvents()

                If .OverrideScheme Then
                    For i As Integer = 0 To AnimationInfo.CustomScheme.Count - 1
                        With AnimationInfo.CustomScheme(i)
                            Dim fillcolor As Color = .Item2
                            Dim pc As PolygonCategory

                            'create new category for symbology
                            If i = 0 Then
                                pc = New PolygonCategory(AnimationInfo.Colors(0), Color.Gray, 0)
                                pc.FilterExpression = String.Format("[{0}]<={1:0.000}", "RESULT", .Item1)
                                pc.LegendText = String.Format("<{0:0.000}", .Item1)
                                polyScheme.AddCategory(pc)
                            End If
                            pc = New PolygonCategory(fillcolor, Color.Gray, 0)
                            If i = AnimationInfo.CustomScheme.Count - 1 Then
                                pc.FilterExpression = String.Format("[{0}]>={1:0.000}", "RESULT", .Item1)
                                pc.LegendText = String.Format(">{0:0.000}", .Item1)
                            Else
                                pc.FilterExpression = String.Format("[{0}]>={1:0.000} and [{0}]<={2:0.000}", "RESULT", .Item1, AnimationInfo.CustomScheme(i + 1).Item1)
                                pc.LegendText = String.Format("{0:0.000} - {1:0.000}", .Item1, AnimationInfo.CustomScheme(i + 1).Item1)
                            End If
                            'add category to scheme
                            polyScheme.AddCategory(pc)
                        End With
                    Next
                Else
                    Static IsDefaultScheme As Boolean = False
                    'If IsDefaultScheme Then Exit Sub 'so don't keep redrawing
                    If cboVariable.Text = "None" Then
                        picLegend.Visible = False
                        If .DataLayer IsNot Nothing Then
                            'remove all but first category then reset color to light blue and 50% opacity
                            If .DataLayer.Symbology IsNot Nothing Then
                                .DataLayer.Symbology.Categories.RemoveRange(1, .DataLayer.Symbology.Categories.Count - 1)
                                With CType(.DataLayer.Symbolizer, DotSpatial.Symbology.PolygonSymbolizer)
                                    Dim sp As New DotSpatial.Symbology.SimplePattern()
                                    If .Patterns.Count = 1 AndAlso TypeOf (.Patterns(0)) Is SimplePattern Then sp = .Patterns(0)
                                    sp.FillColor = Color.Blue
                                    sp.Opacity = 0.5
                                    Dim StrokeList As Generic.IList(Of DotSpatial.Symbology.IStroke) = Nothing
                                    If .OutlineSymbolizer IsNot Nothing Then StrokeList = .OutlineSymbolizer.Strokes
                                    With StrokeList
                                        Dim stroke As New DotSpatial.Symbology.SimpleStroke(1, Color.DarkGray)
                                        stroke.DashStyle = Drawing2D.DashStyle.Solid
                                        .Clear()
                                        .Add(stroke)
                                    End With
                                    .Patterns.Clear()
                                    .Patterns.Add(sp)
                                End With
                                With .DataLayer.Symbology.Categories(0)
                                    .FilterExpression = Nothing
                                    .LegendText = ""
                                    .LegendItemVisible = False
                                End With
                            End If
                        End If
                        'following causes screen to refresh multiple times; need to clean up the code to avoid that
                        If .DataLayer IsNot Nothing AndAlso .DataLayer.DataSet IsNot Nothing Then
                            With .DataLayer.DataSet
                                .InvalidateVertices()
                            End With
                        End If
                        IsDefaultScheme = True
                        Exit Sub
                    End If

                    IsDefaultScheme = False

                    Dim NumEntries As Integer = .NumCategories

                    Dim minValue As Double = .MinValue
                    Dim maxValue As Double = .MaxValue
                    If .MinThresholdValue <> Integer.MinValue Then minValue = .MinThresholdValue
                    If .MaxThresholdValue <> Integer.MaxValue Then maxValue = .MaxThresholdValue

                    For i As Int16 = 0 To NumEntries - 1
                        Dim fillcolor As Color = GradientColor(i * 1.0 / (NumEntries - 1))
                        Dim value, catRange As Double
                        If minValue = Double.MaxValue Then
                            Exit Sub
                        Else
                            catRange = (maxValue - minValue) / (NumEntries)
                            If Double.IsNaN(catRange) Or Double.IsInfinity(catRange) Then catRange = 100.0 'arbitrary maximum to avoid error
                            If Statistic() = enumStatistic.Num Then catRange = Math.Ceiling(catRange) 'make integer
                            value = minValue + i * catRange
                        End If

                        'create new category for symbology
                        Dim pc As New DotSpatial.Symbology.PolygonCategory(fillcolor, Color.Gray, 0)

                        pc.FilterExpression = String.Format("[{0}]>={1:0.000} and [{0}]<{2:0.000}", "Result", value, value + catRange)
                        pc.LegendText = String.Format(IIf(Statistic() = enumStatistic.Num, "{0:0}-{1:0}", "{0:0.00}-{1:0.00}"), value, value + catRange)

                        'add category to scheme
                        polyScheme.AddCategory(pc)

                        If i = NumEntries - 1 Then 'add category for out of range or null
                            pc = New DotSpatial.Symbology.PolygonCategory(.Colors(0), Color.Gray, 0)
                            pc.FilterExpression = String.Format("[{0}]<{1:0.000} or [{0}]>{2:0.000} or [{0}] IS NULL or [{0}] = -1", "Result", minValue, maxValue)
                            pc.LegendText = "Out of range"
                            polyScheme.AddCategory(pc)
                        End If

                    Next
                End If

                polyScheme.ResumeChangeEvent()
                polyScheme.ResumeEvents()

                'set scheme to layer
                If .DataLayer IsNot Nothing Then
                    .DataLayer.Symbology = polyScheme

                    'TODO: in some files the above code results in a problem where some shapes are not drawn unless you zoom in; I tried to reinitialize (below) which was necessary for the None PCode,
                    'but I cannot avoid the bad behavior

                    With .DataLayer.DataSet
                        '.InitializeVertices()
                        '.UpdateExtent()
                        '.InvalidateVertices()
                    End With
                    .DataLayer.AssignFastDrawnStates()
                End If

                GISMap.Refresh()

            End With

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub RefreshVectors()
        Const ARROWHEADLENGTH As Double = 0.3 'fraction of total arrow length that head is drawn
        Const ARROWHEADANGLE As Double = 20 'angle away from body
        With AnimationInfo
            If .DataShowVectors AndAlso .CurrentVectors IsNot Nothing AndAlso .DataAttribute <> "" Then
                If .DataVectorLayer Is Nothing Then 'has not been assigned yet, so do so
                    Dim VectorFileName As String = .DataLayer.DataSet.Filename.Replace(".shp", "_Vectors.shp")
                    For Each lyr As IMapLayer In GISMap.GetAllLayers
                        If TypeOf lyr Is MapLineLayer AndAlso CType(lyr, MapLineLayer).DataSet.Filename = VectorFileName Then
                            .DataVectorLayer = lyr
                            Exit For
                        End If
                    Next
                    If .DataVectorLayer Is Nothing Then 'create a new layer and add it
                        .DataVectorLayer = New MapLineLayer
                        .DataVectorLayer.LegendText = .DataLayerName & " Vectors"
                        .DataVectorLayer.Projection = GISMap.Projection
                        With .DataVectorLayer.DataSet.DataTable.Columns
                            .Add("SegID", GetType(Integer))
                            .Add("Magnitude", GetType(Double))
                            .Add("Direction", GetType(Double))
                        End With
                        .DataVectorLayer.DataSet.Filename = VectorFileName
                        GISMap.Layers.Add(.DataVectorLayer)
                        Dim ls As New LineSymbolizer(.DataArrowColor, 1)
                        .DataVectorLayer.Symbolizer = ls
                    End If
                End If
                'clear all features on layer
                .DataVectorLayer.DataSet.Features.Clear()
                'add a new feature for each polygon
                Dim fld As Integer = GetFieldIndex(.DataLayer.DataSet.DataTable, .DataAttribute)

                'data table has all observations for all stations for selected pcode; set filter, find first record, and aggregate data
                For r As Integer = 0 To .DataLayer.DataSet.Features.Count - 1
                    'get centroid of polygon and convert to screen coordinates
                    Dim centroidPt As Coordinate = .DataLayer.DataSet.Features(r).Centroid.Coordinates(0)
                    'get current vector length and angle for this station
                    Dim StaID As String = TestNull(.DataLayer.DataSet.DataTable.Rows(r).Item(fld), "")
                    Dim value As New Tuple(Of Single, Single)(0, 0)
                    If .CurrentVectors.TryGetValue(StaID, value) Then
                        Dim magnitude As Double = value.Item1
                        Dim direction As Double = value.Item2

                        If magnitude > 0 And .MaxVector <> 0 Then
                            'now add vector to the layer
                            'scale the arrow length based on a fraction of the maximum magnitude and convert to screen units
                            Dim len As Double = .DataArrowLength * magnitude / .MaxVector
                            Dim ang As Double = direction
                            Dim coordPt1 As New Coordinate(centroidPt.X - len / 2 * Math.Cos(ang), centroidPt.Y - len / 2 * Math.Sin(ang))
                            Dim coordPt2 As New Coordinate(centroidPt.X + len / 2 * Math.Cos(ang), centroidPt.Y + len / 2 * Math.Sin(ang))
                            Dim len2 As Double = ARROWHEADLENGTH * len 'make arrow length fraction of total arrow body
                            Dim ang2 As Double = ang + (180 - ARROWHEADANGLE) / 180.0 * Math.PI 'sweep back angle
                            Dim coordPt3 As New Coordinate(coordPt2.X + len2 * Math.Cos(ang2), coordPt2.Y + len2 * Math.Sin(ang2))
                            Dim ang3 As Double = ang - (180 - ARROWHEADANGLE) / 180.0 * Math.PI 'sweep back angle
                            Dim coordPt4 As New Coordinate(coordPt2.X + len2 * Math.Cos(ang3), coordPt2.Y + len2 * Math.Sin(ang3))
                            Dim coords(4) As Coordinate
                            For i As Integer = 0 To 4
                                coords(i) = Choose(i + 1, coordPt1, coordPt2, coordPt3, coordPt4, coordPt2)
                            Next
                            Dim feat As Feature = .DataVectorLayer.DataSet.AddFeature(New Feature(FeatureType.Line, coords))
                            If feat IsNot Nothing Then
                                feat.DataRow("SegID") = .DataLayer.DataSet.DataTable.Rows(r)("SegID")
                                feat.DataRow("Magnitude") = magnitude
                                feat.DataRow("Direction") = direction
                            End If
                        End If
                    End If
                    If IsCancelled Then Exit For
                Next
                With .DataVectorLayer.DataSet
                    .InitializeVertices()
                    .UpdateExtent()
                    .InvalidateVertices()
                End With
                If Not .IsAnimating Then
                    .DataVectorLayer.DataSet.Save()
                End If
            End If
        End With
    End Sub

    Friend Sub DrawLegend()
        Try

            If AnimationInfo.DataLayer Is Nothing OrElse AnimationInfo.DataLayer.Symbology.NumCategories = 0 Then Exit Sub

            With Me.picLegend
                Dim Font As New Font("Segoe UI", 8)
                Dim w As Integer = 0
                For i As Integer = 1 To AnimationInfo.DataLayer.Symbology.NumCategories - 2
                    w = Math.Max(w, .CreateGraphics.MeasureString(AnimationInfo.DataLayer.Symbology.Categories(i).LegendText, Font).Width)
                Next
                .Width = w * 1.1

                If .Width <= 0 Or .Height <= 0 Then Exit Sub

                Dim bm As New Bitmap(.Width, .Height)
                Dim g As Graphics = Graphics.FromImage(bm)

                g.Clear(Color.White)
                Dim BoxHeight As Integer = g.MeasureString("X", Font).Height * 1.2

                '11/10/14 - alway show same number as items as categories; first category is <min value so omit

                Dim NumEntries As Integer = AnimationInfo.DataLayer.Symbology.NumCategories
                For i As Integer = 1 To NumEntries - 2
                    Dim s As String = AnimationInfo.DataLayer.Symbology.Categories(i).LegendText
                    Dim fillcolor As Color = AnimationInfo.DataLayer.Symbology.Categories(i).GetColor
                    Dim br As New SolidBrush(fillcolor)
                    g.FillRectangle(br, New Rectangle(0, BoxHeight * (i - 1), .Width, BoxHeight))
                    Dim fmt As New Drawing.StringFormat
                    fmt.Alignment = StringAlignment.Center
                    fmt.LineAlignment = StringAlignment.Center
                    Dim pnt As PointF
                    'draw white on black
                    pnt = New PointF(.Width / 2 + 1, (i - 0.5) * BoxHeight + 1)
                    g.DrawString(s, Font, Brushes.Black, pnt, fmt)
                    pnt = New PointF(.Width / 2, (i - 0.5) * BoxHeight)
                    g.DrawString(s, Font, Brushes.White, pnt, fmt)
                Next

                .CreateGraphics.DrawImage(bm, 0, 0)

                'save bitmap object in case want to copy to clipboard later
                .Tag = bm

            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    'Private Sub DrawLegend()
    '    Try
    '        If cboVariable.Text = "None" Then
    '            picLegend.Visible = False
    '            Exit Sub
    '        End If

    '        'create legend squares samewidth as picturebox, and height = 12 points

    '        With AnimationInfo
    '            Me.picLegend.Font = New Font("Arial", 8)
    '            Dim g As Graphics = Me.picLegend.CreateGraphics
    '            g.Clear(Color.White)
    '            Dim BoxHeight As Integer = g.MeasureString("0.00", Me.picLegend.Font).Height * 1.5
    '            Dim NumEntries As Integer = Math.Min(.NumLegendItems, .NumCategories)
    '            If .NumLegendItems = 0 Then
    '                NumEntries = Int(Me.picLegend.Height / BoxHeight)
    '            End If
    '            If NumEntries <= 1 Then Exit Sub
    '            NumEntries = Math.Min(NumEntries, .NumCategories)
    '            Dim minValue As Double = .MinValue
    '            Dim maxValue As Double = .MaxValue
    '            If .MinThresholdValue <> Integer.MinValue Then minValue = .MinThresholdValue
    '            If .MaxThresholdValue <> Integer.MaxValue Then maxValue = .MaxThresholdValue

    '            For i As Int16 = 0 To NumEntries - 1
    '                Dim fillcolor As Color = GradientColor(i * 1.0 / (NumEntries - 1))
    '                Dim br As New SolidBrush(fillcolor)
    '                g.FillRectangle(br, New Rectangle(0, BoxHeight * i, Me.picLegend.Width, BoxHeight))
    '                Dim fmt As New Drawing.StringFormat
    '                fmt.Alignment = StringAlignment.Center
    '                fmt.LineAlignment = StringAlignment.Center
    '                Dim pnt As PointF
    '                'draw white on black
    '                pnt = New PointF(Me.picLegend.Width / 2 + 1, (i + 0.5) * BoxHeight + 1)
    '                Dim value, catRange As Double
    '                If minValue <> Double.MaxValue Then
    '                    catRange = (maxValue - minValue) / NumEntries
    '                    If Statistic() = enumStatistic.Num Then catRange = Math.Ceiling(catRange) 'make integer
    '                    value = minValue + (i + 0.5) * catRange
    '                End If
    '                g.DrawString(value.ToString("N3"), Me.picLegend.Font, Brushes.Black, pnt, fmt)
    '                pnt = New PointF(Me.picLegend.Width / 2, (i + 0.5) * BoxHeight)
    '                g.DrawString(value.ToString("N3"), Me.picLegend.Font, Brushes.White, pnt, fmt)
    '            Next
    '        End With
    '    Catch ex As Exception
    '        ErrorMsg(, ex)
    '    End Try
    'End Sub

    Private Sub mnuViewLegend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuViewLegend.Click, tbLegend.Click
        If AnimationInfo.DataLayer Is Nothing Then Exit Sub
        mnuViewLegend.Checked = Not mnuViewLegend.Checked
        picLegend.Visible = mnuViewLegend.Checked
    End Sub

    Private Sub mnuDataOptions_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuDataOptions.Click, mnuLegendOptions.Click, tbDataOptions.Click
        Try
            With New frmGISOptions
                .cboColor0.Text = AnimationInfo.Colors(0).Name
                .cboColor1.Text = AnimationInfo.Colors(1).Name
                .cboColor2.Text = AnimationInfo.Colors(2).Name
                .cboColor3.Text = AnimationInfo.Colors(3).Name
                .cboColor4.Text = AnimationInfo.Colors(4).Name
                .chkInvert.Checked = AnimationInfo.InvertColor
                .txtThresholdMin.Text = AnimationInfo.MinThresholdValue
                .txtThresholdMax.Text = AnimationInfo.MaxThresholdValue
                .txtValueCategories.Value = AnimationInfo.NumCategories
                .cboScalingOption.SelectedIndex = AnimationInfo.ScalingOption
                .chkOverrideScheme.Checked = AnimationInfo.OverrideScheme

                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    AnimationInfo.Colors(0) = Color.FromName(.cboColor0.Text)
                    AnimationInfo.Colors(1) = Color.FromName(.cboColor1.Text)
                    AnimationInfo.Colors(2) = Color.FromName(.cboColor2.Text)
                    AnimationInfo.Colors(3) = Color.FromName(.cboColor3.Text)
                    AnimationInfo.Colors(4) = Color.FromName(.cboColor4.Text)
                    AnimationInfo.InvertColor = .chkInvert.Checked
                    AnimationInfo.MinThresholdValue = .txtThresholdMin.Text
                    AnimationInfo.MaxThresholdValue = .txtThresholdMax.Text
                    AnimationInfo.NumCategories = .txtValueCategories.Value
                    AnimationInfo.ScalingOption = .cboScalingOption.SelectedIndex
                    AnimationInfo.OverrideScheme = .chkOverrideScheme.Checked

                    If .chkOverrideScheme.Checked AndAlso GISForm.AnimationInfo.CustomScheme.Count = 0 Then
                        mnuLayerColoringScheme.PerformClick()
                    Else
                        RefreshStationsDisplay()
                        RefreshPolygons()
                        ResetAnimation()
                        DrawLegend()
                    End If

                    IsModified = True

                End If
                .Dispose()
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            ProgressComplete()
        End Try
    End Sub

    Private Sub mnuFileMovie_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuFileMovie.Click, tbSaveMovie.Click
        If Aggregate = enumAggregate.POR Then
            WarningMsg("You cannot create a movie unless the aggregation period is something other than Period of Record.")
            Exit Sub
        End If
        'see http://www.codeproject.com/Articles/7388/A-Simple-C-Wrapper-for-the-AviFile-Library
        Dim aviMgr As AviFile.AviManager = Nothing
        Dim MovieFile As String = ""
        Dim Success As Boolean = False
        Try
            Dim ThisLabel As String = [Enum].GetName(GetType(enumStatistic), Statistic).ToUpper & "_" & cboVariable.Text.Replace("(", "").Replace(")", "").Replace("/", "")
            With New SaveFileDialog
                .FileName = IO.Path.ChangeExtension(GISFileName, ".avi")
                .AddExtension = True
                .CheckPathExists = True
                .CreatePrompt = False
                .DefaultExt = ".avi"
                .Filter = "Movie File (*.avi)|*.avi"
                .FilterIndex = 0
                .OverwritePrompt = True
                .Title = "Create Animation Movie"
                .ValidateNames = True
                .FileName = String.Format("WRDBGIS_{0}.avi", ThisLabel)
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then MovieFile = .FileName
                .Dispose()
            End With

            If MovieFile = "" Then Exit Sub

            Try
                If My.Computer.FileSystem.FileExists(MovieFile) Then My.Computer.FileSystem.DeleteFile(MovieFile)
            Catch ex As Exception
                ErrorMsg("Unable to delete old video file; it might be open by another application.", ex)
                Exit Sub
            End Try

            Dim IsArranged As Boolean = False
            For Each f As WRDB.Graph.frmGraph In GraphForms()
                If f.FormBorderStyle <> Windows.Forms.FormBorderStyle.Sizable Then IsArranged = True : Exit For
            Next

            Dim EntireScreen As Boolean = False
            If IsArranged Then
                Select Case frmSelectOption.SelectOption(Me, "Capture Entire Screen", "One or more Graph forms are displayed and arranged. What do you want to do?", "Do this for the remainder of the session", "Capture only the GIS map and legend (if displayed).", "Capture the entire screen, including GIS form and all Graph forms.")
                    Case frmSelectOption.enumResult.Button1 : EntireScreen = False
                    Case frmSelectOption.enumResult.Button2 : EntireScreen = True
                    Case frmSelectOption.enumResult.Cancel : Exit Sub
                End Select
            End If

            Dim dtStart As String = InputBox("Enter desired starting date for animation:", "Save Movie", AnimationInfo.MinDate.ToString("d"))
            If dtStart = "" Or Not IsDate(dtStart) Then Exit Sub
            Dim dtEnd As String = InputBox("Enter desired ending date for animation:", "Save Movie", AnimationInfo.MaxDate.ToString("d"))
            If dtEnd = "" Or Not IsDate(dtEnd) Then Exit Sub
            AnimationInfo.ThisDate = CDate(dtStart)

            With trkAnim

                'start from current position
                '.Value = .Minimum 

                'start from requested position
                .Value = Math.Max(0, AnimationInfo.dsData.Dates.IndexOf(CDate(dtStart))) 'if cannot find date, start at beginning

                Dim firstStep As Boolean = True

                Try
                    aviMgr = New AviFile.AviManager(MovieFile, False)
                Catch ex As Exception
                    ErrorMsg("Unable to create video file; it might be open by another application.", ex)
                    Exit Sub
                End Try

                Dim aviStr As AviFile.VideoStream = Nothing

                tblData.Visible = True

                tbAnimBeg.Enabled = False
                tbAnimEnd.Enabled = False
                tbAnimNext.Enabled = False
                tbAnimPrev.Enabled = False
                tbAnimSpeed.Enabled = False
                tbAnimPause.Enabled = False
                tbAnimPlay.Enabled = False

                Do
                    Application.DoEvents()
                    If firstStep Then
                        Try
                            Dim framerate As Double = Choose(AnimationInfo.Speed, 2.4, 3, 4.8, 8, 12, 12, 12) 'determined empirically
                            aviStr = aviMgr.AddVideoStream(True, framerate, MapSnapshot(EntireScreen))
                        Catch ex As Exception
                            ErrorMsg("Unable to create video file; it might be open by another application or the selected compression scheme may be unsupported on your computer.\n\nNote: you may have to restart this application to release the file lock on the AVI file just created.", ex)
                            If aviStr IsNot Nothing Then aviStr.Close()
                            Success = False
                            Exit Sub
                        End Try
                        'set following so that other forms don't obscure image and get captured; there are still a few ways you could get in trouble (e.g., if you minimize by clicking on task bar)
                        Me.TopMost = True
                        Me.MaximizeBox = False
                        Me.MinimizeBox = False
                        Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle
                        firstStep = False
                    Else
                        aviStr.AddFrame(MapSnapshot(EntireScreen))
                    End If
                    If IsCancelled Or .Value + .TickFrequency > .Maximum Then Exit Do
                    .Value += .TickFrequency
                    If Not tblAnim.Visible Then Exit Do 'user clicks on "X" of animation toolbar to stop avi capture
                    If AnimationInfo.ThisDate > CDate(dtEnd) Then Exit Do
                Loop

                '.Value = .Minimum
            End With

            Success = True
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Me.FormBorderStyle = Windows.Forms.FormBorderStyle.Sizable
            Me.TopMost = False
            Me.MaximizeBox = True
            Me.MinimizeBox = True
            tblData.Visible = True 'had been hidden if click "x"
            tbAnimBeg.Enabled = True
            tbAnimEnd.Enabled = True
            tbAnimNext.Enabled = True
            tbAnimPrev.Enabled = True
            tbAnimSpeed.Enabled = True
            tbAnimPause.Enabled = True
            tbAnimPlay.Enabled = True
            If aviMgr IsNot Nothing Then aviMgr.Close()
            If Success And Not String.IsNullOrEmpty(MovieFile) Then Process.Start(MovieFile)
        End Try
    End Sub

    Private Function MapSnapshot(EntireScreen As Boolean) As Bitmap
        Try
            'take snapshot of entire form
            Dim x, y, w, h As Integer
            If EntireScreen Then
                x = 0
                y = 0
                w = My.Computer.Screen.WorkingArea.Width
                h = My.Computer.Screen.WorkingArea.Height
            Else
                x = Me.Location.X
                y = Me.Location.Y
                w = Me.Width
                h = Me.Height
            End If
            Dim MyImage As New System.Drawing.Bitmap(w, h)

            Dim g2 As System.Drawing.Graphics = System.Drawing.Graphics.FromImage(MyImage)
            g2.CopyFromScreen(x, y, 0, 0, MyImage.Size)
            g2.Dispose()

            If EntireScreen Then 'return entire screen
                Return MyImage
            Else 'crop only the map and legend
                Return Crop(MyImage)
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
            Return Nothing
        End Try
    End Function

    Private Function Crop(ByVal bmForm As Bitmap) As Bitmap
        Try

            'copy part of form that has map and legend
            Dim x, w, h As Integer
            Dim dx As Integer, dy As Integer

            If Me.WindowState = FormWindowState.Minimized Then
                WarningMsg("You cannot capture a movie if the form is minimized!")
                Me.WindowState = FormWindowState.Normal
            End If

            If picLegend.Visible Then
                With picLegend
                    x = .PointToScreen(New Drawing.Point(.Width, .Height)).X 'x coordinate of right edge of legend
                End With
            Else
                With GISMap
                    x = .PointToScreen(New Drawing.Point(.Width, .Height)).X 'x coordinate of right edge of map
                End With
            End If
            With GISMap
                w = x - .PointToScreen(New Drawing.Point(0, 0)).X
                h = .Height
                dx = .PointToScreen(.Location).X - Me.Location.X
                dy = .PointToScreen(.Location).Y - Me.Location.Y
            End With

            Dim bmMap As New Bitmap(w, h)

            Dim g2 As Graphics = Graphics.FromImage(bmMap)

            Dim dstRect As New Rectangle(0, 0, w, h)
            Dim srcRect As New Rectangle(New System.Drawing.Point(dx, dy), New Size(w, h))
            g2.DrawImage(bmForm, dstRect, srcRect, GraphicsUnit.Pixel)

            Dim fnt As New Font(picLegend.Font.FontFamily, CSng(picLegend.Font.SizeInPoints * 1.5), FontStyle.Bold)
            Dim fmt As New Drawing.StringFormat
            fmt.Alignment = StringAlignment.Near
            fmt.LineAlignment = StringAlignment.Near
            Dim s As String = String.Format("{0}{1}{2}: {3:" & MyDateTimeFormat() & "}{4}",
                                            IIf(cboStatistic.SelectedIndex = -1, "", cboStatistic.Text & " of "),
                                            cboVariable.Text.Split(" ")(0),
                                            IIf(cboAggregation.Text = "None", "", " aggregated by " & cboAggregation.Text),
                                            AnimationInfo.ThisDate,
                                            IIf(AnimationInfo.MinThresholdValue <= Integer.MinValue, "", "; Min color threshold=" & AnimationInfo.MinThresholdValue) &
                                            IIf(AnimationInfo.MaxThresholdValue >= Integer.MaxValue, "", "; Max color threshold=" & AnimationInfo.MaxThresholdValue))

            Dim ms As SizeF = g2.MeasureString(s, fnt, 0, fmt)
            g2.FillRectangle(New SolidBrush(GISMap.BackColor), 0, 0, ms.Width + 1, ms.Height + 1)
            'g2.DrawRectangle(Pens.Black, 0, 0, ms.Width + 1, ms.Height + 1)

            'draw black on white (assuming gismap backcolor is usually white) unless background color is black then reverse
            If BackgroundColor = Color.Black.Name Then
                g2.DrawString(s, fnt, Brushes.Black, New Drawing.Point(1, 1), fmt)
                g2.DrawString(s, fnt, Brushes.White, New Drawing.Point(0, 0), fmt)
            Else
                g2.DrawString(s, fnt, Brushes.White, New Drawing.Point(1, 1), fmt)
                g2.DrawString(s, fnt, Brushes.Black, New Drawing.Point(0, 0), fmt)
            End If

            Return bmMap

        Catch ex As Exception
            ErrorMsg(, ex)
            Return Nothing
        End Try
    End Function

    Private Sub mnuViewLabels_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuViewLabels.Click, tbLabels.Click
        If TypeOf GISMap.Layers.SelectedLayer Is FeatureLayer Then
            With CType(GISMap.Layers.SelectedLayer, FeatureLayer)
                If .LabelLayer IsNot Nothing Then
                    .LabelLayer.IsVisible = mnuViewLabels.Checked
                    .ShowLabels = mnuViewLabels.Checked
                End If
            End With
        End If
        RefreshStationsDisplay()
    End Sub

    Friend Delegate Sub ProgressDelegate(Value As Integer, MaxValue As Integer, ProgressText As String)

    Friend Sub Progress(Value As Integer, MaxValue As Integer, ProgressText As String)
        WriteProgress(Value, MaxValue, ProgressText)
    End Sub

    Private Sub picLegend_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles picLegend.Paint
        DrawLegend()
    End Sub

    Private Sub mnuToolsProjectionAssign_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsProjectionAssign.Click
        Try
            With New OpenFileDialog()
                .Filter = DotSpatial.Data.DataManager.DefaultDataManager.DialogReadFilter
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    Dim FileName As String = .FileName
                    Dim ds As DotSpatial.Data.DataSet = DotSpatial.Data.DataManager.DefaultDataManager.OpenFile(FileName)
                    Dim projString As String = ""
                    If ds.Projection Is Nothing Then
                        projString = "(None Assigned)"
                    Else
                        projString = ds.Projection.ToString
                    End If
                    If MessageBox.Show(String.Format("This file's projection is: {0}; do you want to assign a different projection? This will NOT change the coordinates but instead will just change the existing file so a new projection is associated with it.", projString), "Assign Projection", MessageBoxButtons.OKCancel) = Windows.Forms.DialogResult.OK Then
                        With New DotSpatial.Projections.Forms.ProjectionSelectDialog()
                            If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                ds.Projection = .SelectedCoordinateSystem
                                If TypeOf ds Is Raster Then
                                    CType(ds, Raster).Save()
                                Else
                                    CType(ds, Shapefile).Save()
                                End If

                                If WarningMsg("Do you want to add this layer to your map?") = Windows.Forms.DialogResult.OK Then
                                    GISMap.Layers.Add(FileName)
                                End If
                            End If
                        End With
                    End If
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private WithEvents mf As DotSpatial.Plugins.Measure.MapFunctionMeasure

    Private Sub mnuToolsMeasure_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsMeasure.Click
        With GISMap
            .FunctionMode = DotSpatial.Controls.FunctionMode.None
            'if was already added, remove and add again (otherwise, displays prior graphic measurements)
            For Each mapfunc As DotSpatial.Controls.IMapFunction In .MapFunctions
                If TypeOf mapfunc Is DotSpatial.Plugins.Measure.MapFunctionMeasure Then
                    .MapFunctions.Remove(mapfunc)
                    Exit For
                End If
            Next
            mf = New DotSpatial.Plugins.Measure.MapFunctionMeasure(GISMap)
            .MapFunctions.Add(mf)
            .Cursor = Cursors.Cross
            mf.Activate()
        End With
    End Sub

    Private Sub mnuToolsProjectionReproject_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsProjectionReproject.Click
        Try
            With New OpenFileDialog()
                .Filter = DotSpatial.Data.DataManager.DefaultDataManager.DialogReadFilter
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    Dim FileName As String = .FileName
                    Dim ds As DotSpatial.Data.DataSet = DotSpatial.Data.DataManager.DefaultDataManager.OpenFile(FileName)
                    If ds.Projection Is Nothing OrElse Not ds.Projection.IsValid Then
                        WarningMsg("This shape file has no projection information assigned to it or it is invalid; it is assumed to use native latitude/longitude coordinates (WGS 1984).")
                        ds.Projection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
                    End If
                    If MessageBox.Show(String.Format("This file's projection is: {0}; do you want to reproject the file to a different projection? This will change the coordinates so that they match the new projection that will be assigned, and you can save the new file under a different name.", ds.Projection.ToString), "Reproject Layer", MessageBoxButtons.OKCancel) = Windows.Forms.DialogResult.OK Then
                        With New DotSpatial.Projections.Forms.ProjectionSelectDialog()
                            If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                Dim newProj = .SelectedCoordinateSystem
                                With New SaveFileDialog
                                    .FileName = IO.Path.GetFileNameWithoutExtension(FileName) & " [" & newProj.ToString & "]"
                                    Dim s As String = DotSpatial.Data.DataManager.DefaultDataManager.DialogReadFilter 'the writefilter is messed up!
                                    .Filter = s
                                    .OverwritePrompt = True
                                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                        'If TypeOf ds Is Raster Then
                                        '    ds.Reproject(newProj)
                                        '    CType(ds, Raster).SaveAs(.FileName)
                                        'Else
                                        '    D4EM.Geo.SpatialOperations.ProjectGrid(FileName, newProj, .FileName)
                                        'End If
                                        If TypeOf ds Is Raster Then
                                            ds.Reproject(newProj)
                                            CType(ds, Raster).SaveAs(.FileName)
                                            'D4EM.Geo.SpatialOperations.ProjectGrid(FileName, newProj, .FileName)
                                        ElseIf TypeOf ds Is FeatureSet Then
                                            ds.Reproject(newProj)
                                            CType(ds, FeatureSet).SaveAs(.FileName, True)
                                        Else
                                            WarningMsg("Unable to reproject layer; unrecognized layer type.")
                                            Exit Sub
                                        End If

                                        If WarningMsg("Do you want to add this reprojected file to your map?") = Windows.Forms.DialogResult.OK Then
                                            GISMap.Layers.Add(.FileName)
                                        End If
                                    End If
                                End With
                            End If
                        End With

                    End If
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuToolsOptions_Click(sender As Object, e As System.EventArgs) Handles mnuToolsOptions.Click
        With New frmOptions
            .txtAnimation.Text = GetSetting(REGAPP, "AutoLoad", "Animation")
            .txtLayer.Text = LayerDirectory
            .txtCache.Text = CacheDirectory
            .cboBackground.Text = BackgroundColor
            If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                SaveSetting(REGAPP, "AutoLoad", "Animation", .txtAnimation.Text)
                LayerDirectory = .txtLayer.Text
                CacheDirectory = .txtCache.Text
                BackgroundColor = .cboBackground.Text
                GISMap.BackColor = Color.FromName(BackgroundColor)
                GISMap.Refresh()
                If Not My.Computer.FileSystem.DirectoryExists(LayerDirectory) Then My.Computer.FileSystem.CreateDirectory(LayerDirectory)
                If Not My.Computer.FileSystem.DirectoryExists(CacheDirectory) Then My.Computer.FileSystem.CreateDirectory(CacheDirectory)
            End If
        End With
    End Sub

    Private Sub mnuLayerShowHide_Click(sender As System.Object, e As System.EventArgs) Handles mnuLayerShowAll.Click, mnuLayerShowAll2.Click, mnuLayerHideAll.Click, mnuLayerHideAll2.Click
        With GISMap
            WriteProgress("Refreshing map..")
            .SuspendLayout()
            .Layers.SuspendEvents()
            For i As Integer = 0 To .Layers.Count - 1
                WriteProgress(i, .Layers.Count - 1, "Refreshing map...")
                .Layers(i).IsVisible = sender Is mnuLayerShowAll Or sender Is mnuLayerShowAll2
            Next
            .Layers.ResumeEvents()
            .ResumeLayout(True)
        End With
        GISLegend.Refresh()
        ProgressComplete()
    End Sub

    Private Sub mnuLayerCollapseAll_Click(sender As Object, e As System.EventArgs) Handles mnuLayerCollapseAll.Click
        Collapse_Expand_All(True)
    End Sub

    Private Sub mnuLayerExpandAll_Click(sender As Object, e As System.EventArgs) Handles mnuLayerExpandAll.Click
        Collapse_Expand_All(False)
    End Sub

    Private Sub Collapse_Expand_All(IsCollapse As Boolean)
        For Each node In GISLegend.RootNodes
            For Each item In node.LegendItems
                Collapse(item, IsCollapse)
            Next
        Next
        GISLegend.RefreshNodes()
        GISLegend.ResetScroll()
        GISLegend.Refresh()
    End Sub

    Private Sub Collapse(item As ILegendItem, IsCollapse As Boolean)
        If item.LegendItems IsNot Nothing Then
            For Each subitem In item.LegendItems
                Collapse(subitem, IsCollapse)
            Next
        End If
        item.IsExpanded = Not IsCollapse
    End Sub

    Private Sub mnuFilePrint_Click(sender As System.Object, e As System.EventArgs) Handles mnuFilePrint.Click
        With New DotSpatial.Controls.LayoutForm
            .Text = "WRDB GIS Print Layout"
            .MapControl = GISMap
            .ShowDialog(Me)
        End With
    End Sub

    'Private Sub mnuLayerBasemap_Click(sender As System.Object, e As System.EventArgs) Handles mnuLayerBasemap.Click
    '    With New DotSpatial.Plugins.WebMap.ServiceProvider

    '    End With
    'End Sub

    'Private Sub mnuLayerBasemap_DropDownItemClicked(sender As Object, e As System.Windows.Forms.ToolStripItemClickedEventArgs) Handles mnuLayerBasemap.DropDownItemClicked
    '    For Each sp As DotSpatial.Plugins.WebMap.ServiceProvider In DotSpatial.Plugins.WebMap.ServiceProvider.GetDefaultServiceProviders
    '        If e.ClickedItem.Text = sp.Name Then
    '            WarningMsg("URL is {0}", sp.Url)
    '            Exit Sub
    '        End If
    '    Next
    'End Sub

    'Private Sub mnuLayerBasemap_DropDownOpening(sender As Object, e As System.EventArgs) Handles mnuLayerBasemap.DropDownOpening
    '    mnuLayerBasemap.DropDownItems.Clear()
    '    For Each sp As DotSpatial.Plugins.WebMap.ServiceProvider In DotSpatial.Plugins.WebMap.ServiceProvider.GetDefaultServiceProviders
    '        mnuLayerBasemap.DropDownItems.Add(sp.Name)
    '    Next
    'End Sub

    Private Sub mnuToolsProjectionOther_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsProjectionOther.Click
        Try
            If Not CheckIfCanReprojectLayers() Then Exit Sub
            With New DotSpatial.Projections.Forms.ProjectionSelectDialog
                'With New MapFrameProjectionDialog(GISMap.MapFrame)
                .SelectedCoordinateSystem = GISMap.MapFrame.Projection
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    GISMap.MapFrame.Projection = .SelectedCoordinateSystem
                    SetExtentsAndReproject()
                End If
                .Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub SetExtentsAndReproject()
        Try

            With GISMap
                ProjectProjection = .Projection
                If .Layers.Count = 0 Then
                    If .Projection.IsLatLon Then
                        .ViewExtents = New Extent(-90, 35, -89, 36)
                    Else
                        .ViewExtents = New Extent(1000, 1000, 5000, 5000)
                    End If
                Else
                    For Each lyr As IMapLayer In .Layers
                        If lyr.CanReproject Then lyr.Reproject(ProjectProjection)
                    Next
                    'don't want to zoom to extents of basemap if there are any feature layers loaded
                    .ZoomToMaxExtent()
                    For Each lyr As IMapLayer In .Layers
                        If TypeOf lyr Is FeatureLayer Then
                            .ViewExtents = CType(lyr, FeatureLayer).Extent
                            .ViewExtents.ExpandBy(.ViewExtents.Height * 0.05) 'for some reason not zoomed out far enough
                            Exit For
                        End If
                    Next
                End If
            End With


        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub mnuToolsProjectionUTM_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsProjectionUTM.Click

        Try

            If Not CheckIfCanReprojectLayers() Then Exit Sub

            With New frmUTMProjection
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    GISMap.MapFrame.Projection = DotSpatial.Projections.KnownCoordinateSystems.Projected.UtmNad1983.GetProjection(String.Format("NAD1983UTMZone{0}N", .SelectedZoneNumber))
                    SetExtentsAndReproject()
                End If
                .Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    Private Sub mnuToolsProjectionWGS_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsProjectionWGS.Click
        Try

            If Not CheckIfCanReprojectLayers() Then Exit Sub

            GISMap.MapFrame.Projection = DotSpatial.Projections.KnownCoordinateSystems.Geographic.World.WGS1984
            SetExtentsAndReproject()

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    ''' <summary>
    ''' Helper routine to move current polygon layer below all non-polygon layers (so filled polygons don't obstruct)
    ''' </summary>
    Friend Sub MoveBelowPolylineLayers(Layer As Layer)
        Try
            If Not TypeOf Layer Is MapPolygonLayer Then Exit Sub
            Dim Group As Object = Nothing
            If Layer.GetParentItem Is Nothing OrElse Not TypeOf Layer.GetParentItem Is MapGroup Then
                'not in a group; compare to other layers at the same root level
                Group = GISMap
            Else
                Group = Layer.GetParentItem
            End If
            With Group
                For i As Integer = 0 To .Layers.IndexOf(Layer) - 1
                    If Not TypeOf .Layers(i) Is MapPolygonLayer Then
                        Layer.LockDispose()
                        Dim LegendText As String = Layer.LegendText
                        .Layers.Remove(Layer)
                        .Layers.Insert(i, Layer)
                        Layer.LegendText = LegendText
                        Layer.UnlockDispose()
                        Exit For
                    End If
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Retrieve layer associated with legend or filename
    ''' </summary>
    ''' <param name="LegendText">Legend text or filename of feature layer</param>
    ''' <returns></returns>
    Friend Function GetLayer(LegendText As String) As ILayer
        For Each lyr As ILayer In GISMap.GetAllLayers
            If String.Equals(lyr.LegendText, LegendText, StringComparison.OrdinalIgnoreCase) Then Return lyr
            'also compare against filename
            If TypeOf lyr Is FeatureLayer AndAlso String.Equals(CType(lyr, FeatureLayer).DataSet.Filename, LegendText, StringComparison.OrdinalIgnoreCase) Then Return lyr
        Next
        Return Nothing
    End Function

    Friend Function FindFeatureIndex(lyr As FeatureLayer, FieldName As String, FieldValue As String) As Integer
        Dim dr() As DataRow = lyr.DataSet.DataTable.Select(String.Format("[{0}]='{1}'", FieldName, FieldValue))
        If dr.Length = 0 Then Return -1
        Return lyr.DataSet.DataTable.Rows.IndexOf(dr(0))
    End Function

    Friend Function OverlappingPolygons(lyr1 As MapPolygonLayer, fIndex1 As Integer, lyr2 As MapPolygonLayer, fIndex2 As Integer) As Boolean
        Dim poly1 As Polygon = lyr1.DataSet.Features(fIndex1).BasicGeometry
        Dim poly2 As Polygon = lyr2.DataSet.Features(fIndex2).BasicGeometry
        Return poly1.Intersects(poly2)
    End Function

    Friend Function AreaOverlappingPolygons(lyr1 As MapPolygonLayer, fIndex1 As Integer, lyr2 As MapPolygonLayer, fIndex2 As Integer) As Double
        Dim poly1 As Polygon = lyr1.DataSet.Features(fIndex1).BasicGeometry
        Dim poly2 As Polygon = lyr2.DataSet.Features(fIndex2).BasicGeometry
        Dim poly3 As Geometry = poly1.Intersection(poly2)
        Return poly3.Area
    End Function

    Private Sub mnuToolsWCS_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsWCS.Click
        With New frmWCS
            .ShowDialog(Me)
        End With
    End Sub

    Private Sub mnuLayerProperties_Click(sender As System.Object, e As System.EventArgs) Handles mnuLayerProperties.Click
        If GISMap.Layers.SelectedLayer IsNot Nothing AndAlso GISMap.Layers.SelectedLayer.LegendType = LegendType.Layer Then
            With New DotSpatial.Symbology.Forms.LayerDialog(GISMap.Layers.SelectedLayer, New DotSpatial.Symbology.Forms.FeatureCategoryControl)
                .ShowDialog(Me)
            End With
        End If
    End Sub

    Private Sub mnuLayerBasemap_DropDownItemClicked(sender As Object, e As ToolStripItemClickedEventArgs) Handles mnuLayerBasemap.DropDownItemClicked
        Application.DoEvents()
        If mnuLayerBasemap.DropDownItems.IndexOf(e.ClickedItem) <= 1 Then Exit Sub 'clicked on opacity or separator
        SetBasemap(e.ClickedItem.Text)
        IsModified = True
    End Sub

    Private Sub cboLayerBasemap_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboLayerBasemap.SelectedIndexChanged
        If Not GISMap.Enabled Then Exit Sub
        Application.DoEvents()
        SetBasemap(cboLayerBasemap.Text)
        IsModified = True
    End Sub

    Private Sub WebMap_Progress(Text As String) Handles WebMap.Progress
        If String.IsNullOrEmpty(Text) Then
            ProgressComplete()
        Else
            WriteProgress(Text)
        End If
    End Sub

    Private Sub mnuLayerBasemapOpacity_Click(sender As Object, e As EventArgs) Handles mnuLayerBasemapOpacity.Click
        Dim s As String = InputBox("Enter the desired opacity to be used for all basemaps from 1 to 100. 100 is fully opaque (you cannot see through it).", "Basemap Opacity", WebMap.Opacity)
        If Val(s) > 0 And Val(s) <= 100 Then
            WebMap.Opacity = Val(s)
        End If
    End Sub

    ''' <summary>
    ''' Check all layers to see if some cannot be reprojected (like basemap image layers)
    ''' </summary>
    ''' <returns>True if is OK to continue; otherwise should cancel operation</returns>
    ''' <remarks></remarks>
    Private Function CheckIfCanReprojectLayers() As Boolean
        Dim lstLayers As New Generic.List(Of String)
        For Each lyr As IMapLayer In GISMap.GetAllLayers
            If Not lyr.CanReproject Then
                lstLayers.Add(lyr.LegendText)
            End If
        Next
        If lstLayers.Count = 0 Then Return True
        Dim s As String = "The following layers cannot be reprojected, so if you proceed to change your map projection, items will appear at the wrong location:\n\n"
        For Each item As String In lstLayers
            s &= "\t" & item & "\n"
        Next
        s &= "\nDo you want to proceed anyway?"
        Return WarningMsg(s) = Windows.Forms.DialogResult.OK
    End Function

    Private Sub mnuGraph_Click(sender As Object, e As EventArgs) Handles mnuGraphDepth.Click, mnuGraphFlowDuration.Click, mnuGraphFreq.Click, mnuGraphLong.Click, mnuGraphProb.Click, mnuGraphScatter.Click, mnuGraphTS.Click, mnuGraphWidth.Click
        Dim GraphType As WRDB.Interfaces.clsGraph.enumGraphType = Choose(mnuGraph.DropDownItems.IndexOf(sender) + 1,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.TimeSeries,
                                                                         Nothing,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Profile,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Depth,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Width,
                                                                         Nothing,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Scatter,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Histogram,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.Probability,
                                                                         WRDB.Interfaces.clsGraph.enumGraphType.FlowDuration)
        OutputGraph(GraphType)
    End Sub

    Private Sub tbGraphTS_Click(sender As Object, e As EventArgs) Handles tbGraphTS.Click
        mnuGraphTS.PerformClick()
    End Sub

    Private Sub tbGraphLong_Click(sender As Object, e As EventArgs) Handles tbGraphLong.Click
        mnuGraphLong.PerformClick()
    End Sub

    Private Sub tbGraphDepth_Click(sender As Object, e As EventArgs) Handles tbGraphDepth.Click
        mnuGraphDepth.PerformClick()
    End Sub

    ''' <summary>
    ''' Display selected data in a graph using the selected format
    ''' </summary>
    ''' <param name="GraphType">Standard graph type to show</param>
    ''' <param name="BypassManageSeries">If true, will bypass showing of initial Manage Series dialog</param>
    Friend Sub OutputGraph(GraphType As WRDB.Interfaces.clsGraph.enumGraphType, Optional BypassManageSeries As Boolean = False)
        Try
            Dim o As System.Runtime.Remoting.ObjectHandle = System.Activator.CreateInstanceFrom(My.Application.Info.DirectoryPath & "\WRDBGraphLib.dll", "WRDB.Graph.clsGraph")
            Dim g As WRDB.Interfaces.clsGraph = o.Unwrap

            If Mode = enumMode.OutputData Then

                If WRDBStationsLayer Is Nothing Then WarningMsg("You must load a WRDB Stations layer first.") : Exit Sub

                If GraphType = Interfaces.clsGraph.enumGraphType.TimeSeries Or
                   GraphType = Interfaces.clsGraph.enumGraphType.Depth Then

                    'see if any stations are selected; if so, load the series

                    If WRDBStationsLayer.Selection Is Nothing OrElse WRDBStationsLayer.Selection.Count = 0 Then
                        g.ShowGraph(Me, WRDBProject.Name, clsProject.TempData, GraphType, False, "")
                    Else
                        g.ShowGraph(Me, WRDBProject.Name, clsProject.TempData, GraphType, True, "")
                        For Each rownum As Integer In CType(WRDBStationsLayer.Selection, DotSpatial.Symbology.IndexSelection)
                            Dim StationID As String = TestNull(WRDBStationsLayer.DataSet.DataTable.Rows(rownum)("Station_ID"), "")
                            g.AddSeries(StationID, cboVariable.Text)
                        Next
                    End If

                Else

                    g.ShowGraph(Me, WRDBProject.Name, clsProject.TempData, GraphType, False, "")

                End If

            ElseIf Mode = enumMode.Animate Then

                With AnimationInfo

                    If .DataLayer Is Nothing OrElse cboVariable.Text = "None" Then WarningMsg("You must first load and configure a data layer and select a variable to display.") : Exit Sub

                    If .DataLayer.Selection Is Nothing OrElse .DataLayer.Selection.Count = 0 Then

                        g.ShowGraph(Me, .dsData.FileName, GraphType, False)

                    Else

                        Dim allLayers As Boolean = False

                        'note: cboLayer may be disabled if linking by segment id such that layers cannot be determined (like Wasp Builder)
                        If cboLayer.Enabled AndAlso GraphType <> Interfaces.clsGraph.enumGraphType.Depth Then
                            Select Case frmSelectOption.SelectOption(Me, "Layer Selection",
                                                                     "Only a single layer is displayed on the map but you have the option of graphing multiple layers. What do you want to do?",
                                                                     "Use this option for the rest of this session",
                                                                     String.Format("Create a single profile for the currently selected layer ({0}).", cboLayer.Text),
                                                                     "Create profiles for all layers.")
                                Case frmSelectOption.enumResult.Button1 : allLayers = False
                                Case frmSelectOption.enumResult.Button2 : allLayers = True
                                Case frmSelectOption.enumResult.Cancel : Exit Sub
                            End Select
                        End If

                        Static distConv As Double = 1.0
                        Static distOffset As Double = 0.0

                        If GraphType = Interfaces.clsGraph.enumGraphType.Profile Then

                            'prompt so can convert to river miles
                            With New frmGraphProfile
                                .txtConversion.Text = distConv
                                .txtOffset.Text = distOffset
                                If String.IsNullOrEmpty(ProjectProjection.Unit.Name) Then
                                    .lblNote.Text = ""
                                Else
                                    .lblNote.Text = String.Format("Current map projection units: {0}", ProjectProjection.Unit.Name)
                                End If
                                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                    distConv = Val(.txtConversion.Text)
                                    distOffset = Val(.txtOffset.Text)
                                Else
                                    Exit Sub
                                End If
                                .Dispose()
                            End With

                            'add river mile field to attribute table so is easy to reference on the map
                            If Not ActiveFeatureLayer.DataSet.DataTable.Columns.Contains("River Mile") Then ActiveFeatureLayer.DataSet.DataTable.Columns.Add("River Mile", GetType(System.Double))
                            If Not ActiveFeatureLayer.DataSet.DataTable.Columns.Contains("Segment Name") Then ActiveFeatureLayer.DataSet.DataTable.Columns.Add("Segment Name", GetType(System.String))

                            'clear previous entries
                            For Each f In ActiveFeatureLayer.DataSet.Features
                                f.DataRow("River Mile") = DBNull.Value
                                f.DataRow("Segment Name") = DBNull.Value
                            Next

                            If distConv = 0 Then Exit Sub

                        End If

                        g.ShowGraph(Me, .dsData.FileName, GraphType, True)

                        'get list of selected cells; if selected using polyline or xsect, reorder based on first xsect point picked
                        'if lstPolygon is non-empty, assume that selected cells were selected by polygon/polyline/xsect and use starting point

                        Dim lstSelected As New List(Of Feature)
                        If lstPolygon.Count > 0 Then
                            For Each ID As String In CreateOrderedListfromSelection(lstPolygon(0))
                                lstSelected.Add(ActiveFeatureLayer.DataSet.Features(ActiveFeatureLayer.DataSet.SelectIndexByAttribute(String.Format("[{0}]='{1}'", IDField(ActiveFeatureLayer), ID))(0)))
                            Next
                        Else
                            For Each f As Feature In ActiveFeatureLayer.Selection.ToFeatureList
                                lstSelected.Add(f)
                            Next
                        End If

                        Dim depthVar As String = "Depth"
                        Dim dist As Double = 0.0
                        Dim lMin As Integer = cboLayer.SelectedIndex, lMax As Integer = lMin
                        If allLayers Then lMin = 0 : lMax = cboLayer.Items.Count - 1
                        If Not cboLayer.Enabled Then lMin = 0 : lMax = 0

                        For l As Integer = lMin To lMax

                            Dim layernum As String = ""
                            If cboLayer.Enabled Then layernum = " " & cboLayer.Items(l)
                            Dim BranchID As String = "GIS Layer" & layernum

                            Dim dictProfile As New Dictionary(Of Double, String)

                            For i As Integer = 0 To lstSelected.Count - 1
                                Dim StationID As String = ""
                                Select Case .DataLinkageType
                                    Case enumLinkageType.StationID
                                        StationID = TestNull(lstSelected(i).DataRow(.DataAttribute), "")
                                    Case enumLinkageType.SegmentNum
                                        Dim SegID As String = TestNull(lstSelected(i).DataRow(.DataAttribute), "")
                                        If SegID <> "" Then StationID = TestNull(.dsData.StationIDs(SegID - 1), "")
                                    Case enumLinkageType.ParseIJK, enumLinkageType.IJKtoIJK
                                        Dim IJK As String = String.Format("{0},{1},{2}",
                                                                          TestNull(lstSelected(i).DataRow("I"), ""),
                                                                          TestNull(lstSelected(i).DataRow("J"), ""),
                                                                          cboLayer.Items(l))
                                        .dsData.TryGetStationIDFromIJK(IJK, StationID)
                                End Select

                                If StationID <> "" And cboVariable.Text <> "None" Then

                                    Select Case GraphType

                                        Case Interfaces.clsGraph.enumGraphType.Depth

                                            'prompt for depth variable

                                            If i = 0 Then
                                                With New frmGraphDepth
                                                    For v As Integer = 1 To cboVariable.Items.Count - 1
                                                        .cboDepthVar.Items.Add(cboVariable.Items(v))
                                                    Next
                                                    .cboDepthVar.Text = depthVar
                                                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                                        depthVar = .cboDepthVar.Text
                                                    Else
                                                        Exit Sub
                                                    End If
                                                    .Dispose()
                                                End With
                                            End If

                                            g.AddSeries(StationID, cboVariable.Text, depthVar)

                                        Case Interfaces.clsGraph.enumGraphType.Profile

                                            If i = 0 Then
                                                dist = 0.0
                                            Else
                                                dist += lstSelected(i).Centroid.Distance(lstSelected(i - 1).Centroid) * distConv
                                            End If

                                            dictProfile.Add(dist, StationID)

                                            lstSelected(i).DataRow("River Mile") = Math.Round(dist, 3)
                                            lstSelected(i).DataRow("Segment Name") = StationID

                                            If i = lstSelected.Count - 1 Then
                                                g.AddSeries(BranchID, dictProfile, cboVariable.Text, .ThisDate)
                                            End If

                                        Case Interfaces.clsGraph.enumGraphType.Width

                                            If i = 0 Then
                                                dist = 0
                                            Else
                                                dist += lstSelected(i).Centroid.Distance(lstSelected(i - 1).Centroid) * distConv
                                            End If

                                            dictProfile.Add(dist, StationID)

                                            If i = lstSelected.Count - 1 Then
                                                g.AddSeries(BranchID, dictProfile, cboVariable.Text, .ThisDate)
                                            End If

                                        Case Else

                                            g.AddSeries(StationID, cboVariable.Text)

                                    End Select
                                End If
                            Next i

                        Next l

                    End If

                End With
            End If

            'save the class instance so can
            Application.OpenForms(Application.OpenForms.Count - 1).Tag = g

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub mnuToolsBMDUtil_Click(sender As Object, e As EventArgs) Handles mnuToolsBMDUtil.Click
        Try

            Process.Start(IO.Path.Combine(My.Application.Info.DirectoryPath, "BMDUtil.exe"))

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub mnuSelectByAttribute_Click(sender As Object, e As EventArgs) Handles mnuSelectByAttribute.Click, tbSelectByAttribute.Click
        Try

            If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer from which shapes will be selected.") : Exit Sub

            MapMode = enumMapMode.Select

            With New frmSelectAttribute(GISMap.Layers.SelectedLayer)
                .ShowDialog(Me)
                .Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    Private Sub mnuSelectByXSect_Click(sender As Object, e As EventArgs) Handles mnuSelectByXSect.Click, tbSelectByXSect.Click
        If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer from which shapes will be selected.") : Exit Sub
        MapMode = enumMapMode.XSect
    End Sub

    Private Sub mnuSelectByPolyline_Click(sender As Object, e As EventArgs) Handles mnuSelectByPolyline.Click, tbSelectByPolyline.Click
        If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer from which shapes will be selected.") : Exit Sub
        ActiveFeatureLayer.UnSelectAll()
        MapMode = enumMapMode.PolylineSelect
    End Sub

    Private Sub mnuSelectByLayer_Click(sender As Object, e As EventArgs) Handles mnuSelectByLayer.Click, tbSelectByLayer.Click
        Try

            If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer from which shapes will be selected.") : Exit Sub

            GISMap.FunctionMode = FunctionMode.Select
            MapMode = enumMapMode.Select

            With New frmSelectByPoly
                For Each lyr In GISForm.GISMap.Layers
                    If lyr IsNot ActiveFeatureLayer AndAlso (LayerType(lyr) = enumLayerType.Polygon Or LayerType(lyr) = enumLayerType.Line) Then
                        .cboPolygonLayer.Items.Add(lyr.LegendText)
                    End If
                Next
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK AndAlso .cboPolygonLayer.SelectedIndex <> -1 Then
                    Dim lyrPoly As FeatureLayer = GetLayer(.cboPolygonLayer.Text)
                    SelectByPoly(lyrPoly)
                End If
                '.Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    ''' <summary>
    ''' After polyline/polygon points have been picked and ESC pressed, select shapes and discard
    ''' </summary>
    Private Sub SelectByPoly()
        Try
            If MapMode = enumMapMode.PolygonSelect Then lstPolygon.Add(lstPolygon(0)) 'close the polygon
            Dim fPoly As New Feature(IIf(MapMode = enumMapMode.PolygonSelect, FeatureType.Polygon, FeatureType.Line), lstPolygon)
            SelectByPoly(fPoly)
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            'MapMode = enumMapMode.Select
        End Try
    End Sub

    ''' <summary>
    ''' Select using polyline/polygon on specified layer
    ''' </summary>
    ''' <param name="lyrPoly">Layer containing polylines/polygons</param>
    Private Sub SelectByPoly(lyrPoly As FeatureLayer)
        Try
            ActiveFeatureLayer.UnSelectAll()
            For Each fPoly As Feature In lyrPoly.DataSet.Features
                SelectByPoly(fPoly)
                'reset polyline list in case want to plot profile from beginning of line
                lstPolygon.Clear()
                lstPolygon.Add(fPoly.Coordinates(0))
            Next

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub SelectByPoly(fPoly As Feature)
        Try

            'test to avoid crashes
            ActiveFeatureLayer.DataSet.InitializeVertices()
            ActiveFeatureLayer.DataSet.InvalidateVertices()
            ActiveFeatureLayer.AssignFastDrawnStates()

            ActiveFeatureLayer.SuspendChangeEvent()
            ActiveFeatureLayer.SelectionEnabled = True

            For f = 0 To ActiveFeatureLayer.DataSet.Features.Count - 1
                Dim fGrid As Feature = ActiveFeatureLayer.DataSet.Features(f)
                If fPoly.Intersects(fGrid.ToShape.ToGeometry) Then
                    ActiveFeatureLayer.SelectByAttribute(String.Format("[{0}]='{1}'", IDField(ActiveFeatureLayer), fGrid.DataRow.Item(IDField(ActiveFeatureLayer))), ModifySelectionMode.Append)
                End If
            Next

            ActiveFeatureLayer.ResumeChangeEvent()

            GISMap.Refresh()

        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Function IDField(Layer As FeatureLayer) As String
        With Layer.DataSet.DataTable.Columns
            If .Contains("ID") Then
                Return "ID"
            ElseIf .Contains("SEGID") Then
                Return "SEGID"
            ElseIf .Contains("WASPID") Then
                Return "WASPID"
            Else
                Throw New ApplicationException("Unable to select objects by ID because one of the following standard field names was not found: ID, SEGID, WASPID.")
            End If
        End With
    End Function

    ''' <summary>
    ''' Examine selected cells and create linked list of feature ID values based on closest neighbor
    ''' </summary>
    ''' <param name="StartingPoint">The coordinates of the starting point for the set of linked cells (e.g., start of cross section used to select cells)</param>
    ''' <returns>Linked list of feature IDs</returns>
    Private Function CreateOrderedListfromSelection(StartingPoint As Coordinate) As List(Of String)
        Dim lstOrderedFeatures As New List(Of String)
        If ActiveFeatureLayer.Selection.Count > 0 Then
            Dim fromPoint As Coordinate = StartingPoint
            Do
                AddClosestIndex(fromPoint, lstOrderedFeatures)
                fromPoint = ActiveFeatureLayer.DataSet.SelectByAttribute(String.Format("[{0}]='{1}'", IDField(ActiveFeatureLayer), lstOrderedFeatures.Last)).First.Centroid.Coordinates(0)
            Loop Until lstOrderedFeatures.Count = ActiveFeatureLayer.Selection.Count
        End If
        Return lstOrderedFeatures
    End Function

    ''' <summary>
    ''' Add ID of closest selected feature to specified point 
    ''' </summary>
    ''' <param name="fromCoord"></param>
    ''' <param name="lstOrderedFeatures"></param>
    ''' <remarks></remarks>
    Private Sub AddClosestIndex(fromCoord As Coordinate, ByRef lstOrderedFeatures As List(Of String))
        Dim minDist As Double = Double.MaxValue
        Dim minID As String = ""
        For Each f As Feature In ActiveFeatureLayer.Selection.ToFeatureList
            If Not lstOrderedFeatures.Contains(f.DataRow(IDField(ActiveFeatureLayer))) Then
                Dim dist As Double = fromCoord.Distance(f.Centroid.Coordinates(0))
                If dist < minDist Then
                    minDist = dist
                    minID = f.DataRow(IDField(ActiveFeatureLayer))
                End If
            End If
        Next
        lstOrderedFeatures.Add(minID)
    End Sub

    Private Sub mnuEditLayerCreateVector_Click(sender As Object, e As EventArgs) Handles mnuLayerEditCreateVector.Click
        Select Case frmSelectOption.SelectOption(Me, "Create Vector Layer", "Select the type of vector layer to create and digitize the first shape. Right-click or press ESC when done and save the shape file.", "", "Polygon", "Polyline", "Points")
            Case frmSelectOption.enumResult.Button1
                MapMode = enumMapMode.PolygonCreate
            Case frmSelectOption.enumResult.Button2
                MapMode = enumMapMode.PolylineCreate
            Case frmSelectOption.enumResult.Button3
                MapMode = enumMapMode.PointCreate
        End Select
    End Sub

    Private Sub mnuLayerEditFields_Click(sender As Object, e As ToolStripItemClickedEventArgs) Handles mnuLayerEditFields.DropDownItemClicked
        Try

            With New frmEditTable(ActiveFeatureLayer.DataSet, mnuLayerEditFields.DropDownItems.IndexOf(e.ClickedItem))
                If .ShowDialog = Windows.Forms.DialogResult.OK Then
                    RefreshPolygons()
                End If
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub mnuLayerEditCreateRaster_Click(sender As Object, e As EventArgs) Handles mnuLayerEditCreateRaster.Click
        With New frmCreateRaster
            .ShowDialog()
            .Dispose()
        End With
    End Sub

    Private Sub mnuLayerEditCreateGrid_Click(sender As Object, e As EventArgs) Handles mnuLayerEditCreateGrid.Click
        With New frmCreateGrid
            .ShowDialog()
            .Dispose()
        End With
    End Sub

    ''' <summary>
    ''' After polyline/polygon/points have been picked and ESC pressed, save to shape file
    ''' </summary>
    Private Sub SaveShapes()
        Try

            With New SaveFileDialog
                .InitialDirectory = GetSetting(REGAPP, Me.Name, "AddLayer", LayerDirectory)
                .Filter = DotSpatial.Data.DataManager.DefaultDataManager.VectorWriteFilter
                .AddExtension = True
                .DefaultExt = ".shp"
                .OverwritePrompt = True
                .Title = "Select Vector Layer to Save"
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    Dim lyr As FeatureLayer = Nothing
                    If MapMode = enumMapMode.PolygonCreate Then
                        lyr = New MapPolygonLayer()
                    ElseIf MapMode = enumMapMode.PolylineCreate Then
                        lyr = New MapLineLayer()
                    ElseIf MapMode = enumMapMode.PointCreate Then
                        lyr = New MapPointLayer()
                    End If
                    lyr.LegendText = IO.Path.GetFileNameWithoutExtension(.FileName)
                    lyr.LegendItemVisible = True
                    lyr.DataSet.Filename = .FileName
                    If MapMode = enumMapMode.PolygonCreate Then lstPolygon.Add(lstPolygon(0)) 'close the polygon
                    lyr.DataSet.Features.Add(lstPolygon)
                    lyr.Projection = ProjectProjection
                    lyr.DataSet.SaveAs(.FileName, True)
                    lyr.Dispose()
                    AddLayer(.FileName)
                    GISLegend.Refresh()
                End If
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        Finally

            MapMode = enumMapMode.Select

        End Try
    End Sub

    Private Sub mnuSelectByFile_Click(sender As Object, e As EventArgs) Handles mnuSelectByFile.Click, tbSelectByFile.Click
        Try
            If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer from which shapes will be selected.") : Exit Sub
            lst3DCells.Clear()
            frmInfoMsg.ShowInfoMsg("Select Cells by File", "You will be prompted to select a delimited (comma, space, or tab) text file containing I, J, and optionally K indices for objects that are to be selected. If your attribute table does not contain I, J, K indices, you can provide a single column file containing ID values. The file need not contain an initial header row; comments can be preceded by C, #, or ; characters.")
            With New OpenFileDialog
                .AddExtension = True
                .CheckFileExists = True
                .DefaultExt = ".txt"
                .Filter = "Text files with object list|*.txt"
                If Not String.IsNullOrEmpty(GISFileName) Then .InitialDirectory = IO.Path.GetDirectoryName(GISFileName)
                .Multiselect = False
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    IsSelectingByFile = True
                    Application.DoEvents()
                    With New FileIO.TextFieldParser(.FileName)
                        .CommentTokens = New String() {"C", "#", ";"}
                        .Delimiters = New String() {",", vbTab, " "}
                        .TextFieldType = FileIO.FieldType.Delimited
                        .TrimWhiteSpace = True

                        'if points were selected using xsect, save coordinates of first point so can reorder if want to reuse the selection using Select by File...
                        Dim s As String = .ReadLine
                        If s.StartsWith("First Coord: ") Then
                            Dim ar() As String = s.Substring(14).Split(",")
                            lstPolygon.Clear()
                            lstPolygon.Add(New Coordinate(ar(0), ar(1))) 'first coordinate is used when creating profile to graph
                        End If

                        WriteProgress("Please stand by, selecting cells...")
                        ActiveFeatureLayer.UnSelectAll()
                        ActiveFeatureLayer.SuspendChangeEvent()
                        While Not .EndOfData
                            Dim ijk() As String = .ReadFields
                            If ijk.Length < 1 Or ijk.Length > 3 Then
                                WarningMsg("The text file should contain one to three columns containing (ID), (I,J) or (I,J,K) indices.")
                                Exit Sub
                            End If
                            If ijk.Length >= 2 Then
                                ActiveFeatureLayer.SelectByAttribute(String.Format("[I]={0} AND [J]={1}", ijk(0), ijk(1)), ModifySelectionMode.Append)
                                Dim k As Integer = 0
                                If ijk.Length = 3 AndAlso IsNumeric(ijk(2)) Then k = CInt(ijk(2))
                                Dim t = Tuple.Create(CInt(ijk(0)), CInt(ijk(1)), k)
                                lst3DCells.Add(t)
                            Else
                                ActiveFeatureLayer.SelectByAttribute(String.Format("[ID]={0}", ijk(0)), ModifySelectionMode.Append)
                            End If
                        End While
                        ActiveFeatureLayer.ResumeChangeEvent()
                        ActiveFeatureLayer.SelectByAttribute("[ID]=-999", ModifySelectionMode.Append) 'dummy select to force refresh
                        .Close()
                    End With
                End If
                .Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        Finally

            GISMap.Refresh()
            IsSelectingByFile = False
            ProgressComplete()

        End Try
    End Sub

    Private Sub mnuSaveSelected_Click(sender As Object, e As EventArgs) Handles mnuSelectSave.Click, tbSelectSave.Click
        Try

            If ActiveFeatureLayer Is Nothing Then WarningMsg("Please select the layer and select shapes.") : Exit Sub

            If ActiveFeatureLayer.Selection.Count = 0 Then
                If WarningMsg("No cells are currently selected; do you want to select all cells?") = Windows.Forms.DialogResult.Cancel Then Exit Sub
                ActiveFeatureLayer.SelectAll()
            End If

            With New SaveFileDialog
                .InitialDirectory = IO.Path.GetDirectoryName(ActiveFeatureLayer.DataSet.Filename)
                .FileName = IO.Path.GetFileName(ActiveFeatureLayer.DataSet.Filename.Replace(".shp", "-Selected.shp"))
                .Filter = "All supported types|*.shp;*.txt|ESRI Shape files|*.shp|Text files with object list|*.txt"
                .DefaultExt = ".shp"
                .OverwritePrompt = True
                .Title = String.Format("Save {0} selected cells to new shapefile or text file", ActiveFeatureLayer.Selection.Count)
                If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    If IO.Path.GetExtension(.FileName).Equals(".SHP", StringComparison.OrdinalIgnoreCase) Then
                        Dim lyrSelected As New MapPolygonLayer(ActiveFeatureLayer.Selection.ToFeatureSet)
                        lyrSelected.DataSet.SaveAs(.FileName, True)
                        If WarningMsg("Do you want to add this file as a new layer?") = Windows.Forms.DialogResult.OK Then
                            AddLayer(.FileName)
                        End If
                    Else
                        Dim sw As New IO.StreamWriter(.FileName)
                        'if lstPolygon is non-empty, assume that selected cells were selected by polygon/polyline/xsect and use starting point
                        Dim lstSelected As New List(Of Feature)
                        If lstPolygon.Count > 0 Then
                            For Each ID As Integer In CreateOrderedListfromSelection(lstPolygon(0))
                                lstSelected.Add(ActiveFeatureLayer.DataSet.Features(ActiveFeatureLayer.DataSet.SelectIndexByAttribute("ID=" & ID)(0)))
                            Next
                            sw.WriteLine("First Coord: {0},{1}", lstPolygon(0).X, lstPolygon(0).Y)
                        Else
                            lstSelected.AddRange(ActiveFeatureLayer.Selection.ToFeatureList.ToArray)
                        End If

                        'see if I,J attributes exist; if not, just save ID
                        If ActiveFeatureLayer.DataSet.DataTable.Columns.Contains("I") AndAlso ActiveFeatureLayer.DataSet.DataTable.Columns.Contains("J") Then
                            sw.WriteLine("#I" & vbTab & "J")
                            For Each f In lstSelected
                                sw.WriteLine(f.DataRow("I") & vbTab & f.DataRow("J"))
                            Next
                        Else
                            sw.WriteLine("#ID")
                            For Each f In lstSelected
                                sw.WriteLine(f.DataRow(IDField(ActiveFeatureLayer)))
                            Next
                        End If
                        sw.Close()
                        sw.Dispose()
                    End If
                End If
                .Dispose()
            End With

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub GISMap_MouseUp(sender As Object, e As MouseEventArgs) Handles GISMap.MouseUp

        Try

            Select Case MapMode
                Case enumMapMode.XSect
                    GISMap.FunctionMode = FunctionMode.Select
                    MapMode = enumMapMode.Select
                    Dim CntlPressed As Boolean = (Control.ModifierKeys And Keys.Control) > 0
                    If Not CntlPressed Then
                        ActiveFeatureLayer.UnSelectAll()
                        'test to avoid crashes
                        ActiveFeatureLayer.DataSet.InitializeVertices()
                        ActiveFeatureLayer.DataSet.InvalidateVertices()
                        ActiveFeatureLayer.AssignFastDrawnStates()
                    End If
                    Dim pnt As Coordinate = GISMap.PixelToProj(New Drawing.Point(e.X, e.Y))
                    lstPolygon.Add(pnt)
                    Dim line As New DotSpatial.Topology.LineString(lstPolygon)

                    ActiveFeatureLayer.SuspendChangeEvent()

                    For i = 0 To ActiveFeatureLayer.DataSet.Features.Count - 1
                        If line.Intersects(ActiveFeatureLayer.DataSet.Features(i).ToShape.ToGeometry) Then
                            Dim filter As String = String.Format("[{0}]='{1}'", IDField(ActiveFeatureLayer), ActiveFeatureLayer.DataSet.Features(i).DataRow.Item(IDField(ActiveFeatureLayer)))
                            ActiveFeatureLayer.SelectByAttribute(filter, ModifySelectionMode.Append)
                        End If
                    Next

                    ActiveFeatureLayer.ResumeChangeEvent()
                    GISMap.Refresh()
                    StatusLabel.Text = ""

            End Select

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try
    End Sub

    Private Sub mnuGraphCloseAll_Click(sender As Object, e As EventArgs) Handles mnuGraphCloseAll.Click
        Try

            For Each f As Form In GraphForms()
                f.Close()
            Next

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    Private Function GraphForms() As List(Of WRDB.Graph.frmGraph)
        Dim lst As New List(Of WRDB.Graph.frmGraph)
        For Each f As Form In Application.OpenForms
            If TypeOf f Is WRDB.Graph.frmGraph Then lst.Add(f)
        Next
        Return lst
    End Function

    Private Sub mnuGraphArrange_Click(sender As Object, e As EventArgs) Handles mnuGraphArrange.Click
        Try
            If GraphForms.Count > 0 Then
                With Me
                    .WindowState = FormWindowState.Normal
                    .Location = New System.Drawing.Point(0, 0)
                    .Size = New Size(My.Computer.Screen.WorkingArea.Width / 2, My.Computer.Screen.WorkingArea.Height)
                End With
                For i As Integer = 0 To GraphForms.Count - 1
                    Dim f As WRDB.Graph.frmGraph = GraphForms(i)
                    With f
                        .MinimumSize = New Size(0, 0)
                        .ShowGraphControls(False)
                        .Location = New System.Drawing.Point(My.Computer.Screen.WorkingArea.Width / 2, My.Computer.Screen.WorkingArea.Height * (i) / GraphForms.Count)
                        .Size = New Size(My.Computer.Screen.WorkingArea.Width / 2, My.Computer.Screen.WorkingArea.Height / (GraphForms.Count + 0))
                    End With
                Next
            End If

        Catch ex As Exception

            ErrorMsg(, ex)

        End Try

    End Sub

    Private Sub mnuGraph_DropDownOpening(sender As Object, e As EventArgs) Handles mnuGraph.DropDownOpening
        mnuGraphArrange.Enabled = GraphForms.Count > 0
        mnuGraphCloseAll.Enabled = GraphForms.Count > 0
    End Sub

    Private Sub mnuToolbarMain_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolbarMain.CheckedChanged, mnuTbMain.CheckedChanged
        mnuToolbarMain.Checked = CType(sender, ToolStripMenuItem).Checked
        mnuTbMain.Checked = CType(sender, ToolStripMenuItem).Checked
        tbMain.Visible = mnuToolbarMain.Checked
    End Sub

    Private Sub mnuToolbarAnim_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolbarAnim.CheckedChanged, mnuTbAnim.CheckedChanged
        tblData.Visible = CType(sender, ToolStripMenuItem).Checked
    End Sub

    Private Sub mnuToolbarStatus_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolbarStatus.CheckedChanged, mnuTbStatus.CheckedChanged
        mnuToolbarStatus.Checked = CType(sender, ToolStripMenuItem).Checked
        mnuTbStatus.Checked = CType(sender, ToolStripMenuItem).Checked
        statusMain.Visible = mnuToolbarStatus.Checked
    End Sub

    Private Sub mnuToolbarText_CheckedChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolbarText.CheckedChanged, mnuTbText.CheckedChanged
        mnuToolbarText.Checked = CType(sender, ToolStripMenuItem).Checked
        mnuTbText.Checked = CType(sender, ToolStripMenuItem).Checked
        For Each tbBtn As ToolStripItem In tbMain.Items
            If Not TypeOf tbBtn Is ToolStripSeparator Then
                With CType(tbBtn, Object)
                    If mnuToolbarText.Checked Then
                        .AutoSize = True
                        '.height = 33
                        '.Width = 44
                        .DisplayStyle = ToolStripItemDisplayStyle.ImageAndText
                    Else
                        .AutoSize = True
                        .DisplayStyle = ToolStripItemDisplayStyle.Image
                    End If
                End With
            End If
        Next
    End Sub

    Private Sub tblData_VisibleChanged(sender As Object, e As EventArgs) Handles tblData.VisibleChanged
        mnuToolbarAnim.Checked = tblData.Visible
        mnuTbAnim.Checked = tblData.Visible
    End Sub

    Private Sub mnuLayerColoringScheme_Click(sender As Object, e As EventArgs) Handles mnuLayerColoringScheme.Click

        If AnimationInfo.DataLayer Is Nothing Then
            WarningMsg("The coloring scheme can only be configured for the polygon layer associated with an active data file, but no such layer has been configured yet.")
            Exit Sub
        End If
        ResetAnimation()

        With New frmColorScheme
            .ShowDialog(Me)
            RefreshPolygons()
            DrawLegend()
            .Dispose()
        End With
    End Sub

    Private Sub mnuLayerEditClipRaster_Click(sender As Object, e As EventArgs) Handles mnuLayerEditClipRaster.Click, mnuLayerEditClipGrid.Click
        With New frmClip
            .ShowDialog(Me)
        End With
    End Sub

    Private Sub mnuDataDateTime_Click(sender As Object, e As EventArgs) Handles mnuDataDateTime.Click
        With AnimationInfo

            Dim s As String = InputBox("Enter date or date-time you wish to jump to:", "Jump to Date", .MinDate.ToShortDateString)
            If s.Trim = "" Then Exit Sub
            Dim dt As Date = .MinDate
            If DateTime.TryParse(s, dt) Then
                Select Case .TimeUnits
                    Case enumTimeUnits.Minute : trkAnim.Value = Math.Max(0, Math.Min(trkAnim.Maximum, dt.Subtract(.MinDate).TotalMinutes))
                    Case enumTimeUnits.Hour : trkAnim.Value = Math.Max(0, Math.Min(trkAnim.Maximum, dt.Subtract(.MinDate).TotalHours))
                    Case enumTimeUnits.Day : trkAnim.Value = Math.Max(0, Math.Min(trkAnim.Maximum, dt.Subtract(.MinDate).TotalDays))
                    Case enumTimeUnits.Month : trkAnim.Value = Math.Max(0, Math.Min(trkAnim.Maximum, dt.Subtract(.MinDate).TotalDays / 30))
                    Case enumTimeUnits.Year : trkAnim.Value = dt.Year - .MinDate.Year
                End Select
            End If
        End With
    End Sub

    Private Sub mnuLayers_DropDownOpening(sender As Object, e As EventArgs) Handles mnuLayers.DropDownOpening
        mnuLayerColoringScheme.Enabled = (AnimationInfo.DataLayer IsNot Nothing)
    End Sub

End Class

' ''' <summary>
' ''' This class exposes the MouseWheel event on the DotSpatial Map control
' ''' </summary>
'Public Class MouseWheelMap
'    Inherits DotSpatial.Controls.Map

'    Public Event MouseWheelDownEvent(ByVal sender As Object, ByVal e As MouseEventArgs)

'    ' This is the event that should be intercepted to receive the mouse wheel event
'    'Public Event MouseWheelScrollEvent(ByVal sender As Object, ByVal e As MouseEventArgs)
'    Public Event MouseWheelUpEvent(ByVal sender As Object, ByVal e As MouseEventArgs)
'    Public Const MK_LBUTTON As Integer = &H1
'    Public Const MK_MBUTTON As Integer = &H10
'    Public Const MK_RBUTTON As Integer = &H2
'    Public Const WM_MBUTTONDOWN As Integer = &H207
'    Public Const WM_MBUTTONUP As Integer = &H208
'    Public Const WM_MOUSEMOVE As Integer = &H200

'    Public Const WM_MOUSEWHEEL As Integer = &H20A

'    ' This method traps all windows messages, but only responds to the MouseWheel message
'    Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
'        'If m.Msg = WM_MOUSEWHEEL Or m.Msg = WM_MBUTTONUP Or m.Msg = WM_MBUTTONDOWN Then
'        If m.Msg = WM_MOUSEMOVE Then
'            Dim xpos, ypos, mouseKeys, rotation, delta As Integer
'            xpos = m.LParam.ToInt32() And 65535
'            ypos = m.LParam.ToInt32() / 65536
'            mouseKeys = m.WParam.ToInt32() And 65535
'            rotation = m.WParam.ToInt32() / 65536
'            delta = rotation / 120
'            Dim mb As MouseButtons
'            Select Case mouseKeys
'                Case MK_LBUTTON : mb = Windows.Forms.MouseButtons.Left
'                Case MK_RBUTTON : mb = Windows.Forms.MouseButtons.Right
'                Case MK_MBUTTON : mb = Windows.Forms.MouseButtons.Middle
'                Case Else : mb = Windows.Forms.MouseButtons.None
'            End Select
'            If mb = MouseButtons.Middle Then MyBase.OnMouseMove(New MouseEventArgs(Windows.Forms.MouseButtons.Left, 1, xpos, ypos, delta)) : Exit Sub

'        ElseIf (m.Msg = WM_MBUTTONUP Or m.Msg = WM_MBUTTONDOWN) Then

'            Dim xpos, ypos, mouseKeys, rotation, delta As Integer
'            xpos = m.LParam.ToInt32() And 65535
'            ypos = m.LParam.ToInt32() / 65536
'            mouseKeys = m.WParam.ToInt32() And 65535
'            rotation = m.WParam.ToInt32() / 65536
'            delta = rotation / 120
'            Dim mb As MouseButtons
'            Select Case mouseKeys
'                Case MK_LBUTTON : mb = Windows.Forms.MouseButtons.Left
'                Case MK_RBUTTON : mb = Windows.Forms.MouseButtons.Right
'                Case MK_MBUTTON : mb = Windows.Forms.MouseButtons.Middle
'                Case Else : mb = Windows.Forms.MouseButtons.None
'            End Select

'            Select Case m.Msg
'                'Case WM_MOUSEWHEEL
'                '    RaiseEvent MouseWheelScrollEvent(Me, New MouseEventArgs(mb, 0, xpos, ypos, delta))
'                Case WM_MBUTTONDOWN
'                    MyBase.FunctionMode = DotSpatial.Controls.FunctionMode.Pan
'                    'RaiseEvent MouseWheelDownEvent(Me, New MouseEventArgs(mb, 0, xpos, ypos, delta))
'                Case WM_MBUTTONUP
'                    MyBase.FunctionMode = DotSpatial.Controls.FunctionMode.Select
'                    'RaiseEvent MouseWheelUpEvent(Me, New MouseEventArgs(mb, 0, xpos, ypos, delta))
'            End Select
'            Exit Sub
'        End If

'        MyBase.WndProc(m)
'    End Sub

'End Class

Public Class MapWinProgress
    Implements MapWinUtility.IProgressStatus

    Public Sub Progress(aCurrentPosition As Integer, aLastPosition As Integer) Implements MapWinUtility.IProgressStatus.Progress
        'WriteProgress(GISForm, aCurrentPosition, aLastPosition, "")
        If GISForm.InvokeRequired Then
            GISForm.BeginInvoke(
                Sub()
                    GISForm.Progress(aCurrentPosition, aLastPosition, "")
                End Sub)
        Else
            GISForm.Progress(aCurrentPosition, aLastPosition, "")
        End If
    End Sub

    Public Sub Status(aStatusMessage As String) Implements MapWinUtility.IProgressStatus.Status
        'GISForm.StatusLabel.Text = aStatusMessage
    End Sub
End Class

<System.ComponentModel.Composition.Export(GetType(DotSpatial.Controls.Header.IStatusControl))>
Public Class GISStatusControl
    Implements DotSpatial.Controls.Header.IStatusControl

    <System.ComponentModel.Composition.Import("Shell", GetType(ContainerControl))>
    Private Property Shell As ContainerControl

    Public Sub Add(panel As Header.StatusPanel) Implements Header.IStatusControl.Add

    End Sub

    Public Sub Remove(panel As Header.StatusPanel) Implements Header.IStatusControl.Remove

    End Sub

    Public Sub Progress(key As String, percent As Integer, message As String) Implements IProgressHandler.Progress

    End Sub
End Class


<System.ComponentModel.Composition.Export(GetType(DotSpatial.Controls.Header.IHeaderControl))>
Public Class GISHeaderControl
    Implements DotSpatial.Controls.Header.IHeaderControl

    <System.ComponentModel.Composition.Import("Shell", GetType(ContainerControl))>
    Private Property Shell As ContainerControl

    Public Sub Add(item As DotSpatial.Controls.Header.HeaderItem) Implements DotSpatial.Controls.Header.IHeaderControl.Add

    End Sub

    Public Sub Remove(key As String) Implements DotSpatial.Controls.Header.IHeaderControl.Remove

    End Sub

    Public Sub RemoveAll() Implements DotSpatial.Controls.Header.IHeaderControl.RemoveAll

    End Sub

    Public Event RootItemSelected(sender As Object, e As DotSpatial.Controls.Header.RootItemEventArgs) Implements DotSpatial.Controls.Header.IHeaderControl.RootItemSelected

    Public Sub SelectRoot(key As String) Implements DotSpatial.Controls.Header.IHeaderControl.SelectRoot

    End Sub
End Class

<System.ComponentModel.Composition.Export(GetType(DotSpatial.Controls.Docking.IDockManager))>
Public Class GISDockManager
    Implements DotSpatial.Controls.Docking.IDockManager

    <System.ComponentModel.Composition.Import("Shell", GetType(ContainerControl))>
    Private Property Shell As ContainerControl

    Public Event ActivePanelChanged(sender As Object, e As DotSpatial.Controls.Docking.DockablePanelEventArgs) Implements DotSpatial.Controls.Docking.IDockManager.ActivePanelChanged

    Public Sub Add(panel As DotSpatial.Controls.Docking.DockablePanel) Implements DotSpatial.Controls.Docking.IDockManager.Add
    End Sub

    Public Sub HidePanel(key As String) Implements DotSpatial.Controls.Docking.IDockManager.HidePanel
    End Sub

    Public Event PanelAdded(sender As Object, e As DotSpatial.Controls.Docking.DockablePanelEventArgs) Implements DotSpatial.Controls.Docking.IDockManager.PanelAdded

    Public Event PanelClosed(sender As Object, e As DotSpatial.Controls.Docking.DockablePanelEventArgs) Implements DotSpatial.Controls.Docking.IDockManager.PanelClosed

    Public Event PanelHidden(sender As Object, e As DotSpatial.Controls.Docking.DockablePanelEventArgs) Implements DotSpatial.Controls.Docking.IDockManager.PanelHidden

    Public Event PanelRemoved(sender As Object, e As DotSpatial.Controls.Docking.DockablePanelEventArgs) Implements DotSpatial.Controls.Docking.IDockManager.PanelRemoved

    Public Sub Remove(key As String) Implements DotSpatial.Controls.Docking.IDockManager.Remove
    End Sub

    Public Sub ResetLayout() Implements DotSpatial.Controls.Docking.IDockManager.ResetLayout
    End Sub

    Public Sub SelectPanel(key As String) Implements DotSpatial.Controls.Docking.IDockManager.SelectPanel
    End Sub

    Public Sub ShowPanel(key As String) Implements DotSpatial.Controls.Docking.IDockManager.ShowPanel
    End Sub

End Class

Public Class clsMapProgressHandler
    Implements DotSpatial.Data.IProgressHandler
    Public Sub Progress(key As String, percent As Integer, message As String) Implements IProgressHandler.Progress
        If GISForm.AnimationInfo.IsAnimating Then Exit Sub 'this should not be necessary but I am unable to detach this handler once I set it
        If key = "Ready." Or key = "" Then
            GISForm.ProgressComplete()
        Else
            GISForm.WriteProgress(percent, 100, key)
        End If
    End Sub
End Class

''' <summary>
''' This class is similar to MapFunctionSelectPan but lets you move items and also allow do a 'transparent' zoom/pan while selecting
''' </summary>
''' <remarks></remarks>
Friend Class MapFunctionTool
    Inherits MapFunction

    Private _dragStart As System.Drawing.Point
    Private _isDragging As Boolean
    Private _preventDrag As Boolean
    Private _source As Rectangle

    Private _client As Rectangle
    Private _direction As Integer = 1
    Private _mapFrame As IMapFrame
    Private _sensitivity As Double
    Private _timerInterval As Integer
    Private WithEvents _zoomTimer As Timer
    Private _priorCursor As Cursor

    Sub New(inMap As IMap)
        MyBase.New(inMap)
        _priorCursor = MyBase.Map.Cursor
        inMap.FunctionMode = FunctionMode.None
        MyBase.Map.Cursor = _priorCursor
        Configure()
    End Sub

    Private Sub Configure()
        YieldStyle = YieldStyles.Scroll
        _timerInterval = 100
        _zoomTimer = New Timer() With {.Interval = _timerInterval}
        _client = Rectangle.Empty
        Sensitivity = 0.5
        Name = "ScrollZoom"
    End Sub


    Private Sub _zoomTimer_Tick(sender As Object, e As EventArgs) Handles _zoomTimer.Tick
        _zoomTimer.Stop()
        If _mapFrame Is Nothing Then
            Return
        End If
        _client = Rectangle.Empty
        _mapFrame.ResetExtents()
        Map.IsBusy = False
    End Sub

    ''' <summary>
    ''' Gets or sets the wheel zoom sensitivity.  Increasing makes it more sensitive.  Maximum is 0.5, Minimum is 0.01
    ''' </summary>
    Public Property Sensitivity() As Double
        Get
            Return 1.0 / _sensitivity
        End Get
        Set(value As Double)
            If value > 0.5 Then
                value = 0.5
            ElseIf value < 0.01 Then
                value = 0.01
            End If
            _sensitivity = 1.0 / value
        End Set
    End Property

    Protected Overrides Sub OnMouseWheel(e As DotSpatial.Controls.GeoMouseArgs)
        _zoomTimer.Stop()
        ' if the timer was already started, stop it.
        Dim r As Rectangle = e.Map.MapFrame.View

        ' For multiple zoom steps before redrawing, we actually
        ' want the x coordinate relative to the screen, not
        ' the x coordinate relative to the previously modified view.
        If _client = Rectangle.Empty Then
            _client = r
        End If
        Dim cw As Integer = _client.Width
        Dim ch As Integer = _client.Height

        Dim w As Double = r.Width
        Dim h As Double = r.Height

        If _direction * e.Delta > 0 Then
            Dim inFactor As Double = 2.0 * _sensitivity
            r.Inflate(Convert.ToInt32(-w / inFactor), Convert.ToInt32(-h / inFactor))
            ' try to keep the mouse cursor in the same geographic position
            r.X += Convert.ToInt32((e.X * w / (_sensitivity * cw)) - w / inFactor)
            r.Y += Convert.ToInt32((e.Y * h / (_sensitivity * ch)) - h / inFactor)
        Else
            Dim outFactor As Double = 0.5 * _sensitivity
            r.Inflate(Convert.ToInt32(w / _sensitivity), Convert.ToInt32(h / _sensitivity))
            r.X += Convert.ToInt32(w / _sensitivity - (e.X * w / (outFactor * cw)))
            r.Y += Convert.ToInt32(h / _sensitivity - (e.Y * h / (outFactor * ch)))
        End If

        e.Map.MapFrame.View = r
        e.Map.Invalidate()
        _zoomTimer.Start()
        _mapFrame = e.Map.MapFrame
        Map.IsBusy = True
        MyBase.OnMouseWheel(e)
    End Sub

    Protected Overrides Sub OnMouseDown(e As DotSpatial.Controls.GeoMouseArgs)
        MyBase.OnMouseDown(e)
        If e.Button = MouseButtons.Middle AndAlso _preventDrag = False Then
            MyBase.Map.Cursor = HandCursor
            _isDragging = True
            _dragStart = e.Location
            _source = e.Map.MapFrame.View
            Map.IsBusy = True
        End If
    End Sub

    Protected Overrides Sub OnMouseUp(e As DotSpatial.Controls.GeoMouseArgs)
        MyBase.OnMouseUp(e)
        If e.Button = MouseButtons.Middle And _isDragging Then
            _isDragging = False

            _preventDrag = True
            e.Map.MapFrame.ResetExtents()
            _preventDrag = False
            Map.IsBusy = False

            MyBase.Map.Cursor = _priorCursor  'resume cursor
        End If
    End Sub

    Protected Overrides Sub OnMouseMove(e As DotSpatial.Controls.GeoMouseArgs)
        If _isDragging Then
            Dim diff As New Point() With {.X = _dragStart.X - e.X, .Y = _dragStart.Y - e.Y}
            e.Map.MapFrame.View = New Rectangle(_source.X + diff.X, _source.Y + diff.Y, _source.Width, _source.Height)
            Map.Invalidate()
        End If
        MyBase.OnMouseMove(e)
    End Sub

End Class
